posit-dev / py-shiny

Shiny for Python
https://shiny.posit.co/py/
MIT License
1.33k stars 82 forks source link

Best way to loop a download or image module #1726

Open michaelsong-vbu opened 1 month ago

michaelsong-vbu commented 1 month ago

Hello,

I am learning about modules and found that looping an input component is relatively straightforward using examples. However, looping output components like downloads or images can be tricky. I would like to know the best ways to loop such components.

from shiny import App, module, render, ui
import os

num_extra_rows = 5
extra_ids = [f"row_{i}" for i in range(1, 2 + num_extra_rows)]

@module.ui
def row_ui(row_id):
  return ui.layout_columns(
      ui.input_text("text_in", f'Enter text {row_id}'),
      ui.output_text("text_out"),
      ui.download_button("download_data", "Download CSV")
  )

@module.server
def row_server(input, output, session, row_id):
  input_id = f"text_in_{row_id}"

    @output
    @render.text
    def text_out():
        return f'You entered "{input.text_in()}"'

    @output
    @render.download(
        filename=lambda: f"report.csv"
    )
    def download_data():
        path = os.path.join(os.path.dirname(__file__), "../image/report/", f"report{row_id}.csv")
        return path

app_ui = ui.page_fluid(
  [row_ui(x, x) for x in extra_ids]
)

def server(input, output, session):
  [row_server(x, x) for x in extra_ids]

app = App(app_ui, server)

For example, I want to generate six rows as defined in extra_ids. The input_text and output_text are auto-binded using {row_id}. However, the download_data function always downloads the report with the last row_id, i.e ., reportrow_6.csv. Here is a screenshot:

Image

Every “Download CSV” button outputs reportrow_6.csv instead of the intended reportrow_1.csv, reportrow_2.csv, etc.

I have also tried this for @render.images and encountered the same issue: the last image in the loop is shown for every entry, even though I specify the dynamic {name} like this:

ImgData = {"src": str(dir / "./image/upload_image" / {name}), "width": "100px"}

I might be doing something wrong, but I have not managed to implement dynamic looping for download and image components successfully. In Shiny R, I can use lapply for this purpose with ease. However, I am deeply invested in using Shiny for Python.

Thank you very much in advance for any assistance with this issue.

nsiicm0 commented 1 month ago

You are facing the exact issue that I am describing here: https://github.com/posit-dev/py-shiny/issues/1724 Coincidentally posted it yesterday as well.