quarto-ext / shinylive

Quarto extension to embed Shinylive for Python applications
https://quarto-ext.github.io/shinylive/
MIT License
144 stars 8 forks source link

Could you give an example where data is being loaded locally? #7

Open JoeHasell opened 1 year ago

JoeHasell commented 1 year ago

Hi there!

Thanks very much for your amazing work.

I came to shinylive because I was looking for a way to get my shiny apps onto my netlify webpage without having to host them at e.g. shinyapp.io (where I'd have to pay above a certain useage).

I'm fairly comfortable making shiny apps and with R and RMarkdown. But I'm pretty new to Python, and my technical knowledge of what's going on behind the scenes here is very limited.

My apologies for asking what seems like a very basic question. I managed to get your example up and running in a Quarto web project published to netlify, but I'm struggling to figure out how to make an app that is reading data locally. I don't really get what is going on with the file structures and how to refer to them.

For instance, what I'd like to do is something like this:

def server(input, output, session):
    @output
    @render.table
    def table():

        df = pd.read_csv(WHAT_GOES_HERE?)

        return df

I tried various guesses at 'WHAT_GOES_HERE', and various file structures but couldn't ever find a way to read in the data.

I'd really appreciate it if you could explain, or perhaps put up an example that would help me see how to do this.

Many thanks in advance!

AlFontal commented 1 year ago

Hey, I am actually having trouble with this as I am unable to work with multiple files within a quarto document and I have no clue whether it is even possible to make this work in that kind of setting.

For what you want, however, the nomenclature should be something akin to:

from pathlib import Path

def server(input, output, session):
    @output
    @render.table
    def table():
        infile = Path(__file__).parent / "your_file.csv"
        df = pd.read_csv(infile)
        return df

What I am struggling to get, however, is that file.csv bundled in a standalone shinylive-python cell... I will open an issue for that, however.

JoeHasell commented 1 year ago

Hey @AlFontal – thanks for your comment!

I also tried reading in the data in a python or r cell and then passing the object to the sinylive-python cell, but didn't have any luck with that either.

Glad to hear this isn't me just being dumb! Thanks again for your help.

AlFontal commented 1 year ago

Hey @JoeHasell, I made some advances with this issue today and came up with a horrible solution, but it works.

Hopefully, a way to bundle extra files into the shinylive/pyodide environment using a tag or something will come up...

Basically, the way I solved it is by adapting what is described in another issue (#5) to include a requirements.txt file, which means that you need to completely paste your csv file contents in there. For example, this standalone cell is working within quarto:

```{shinylive-python}
#| standalone: true
#| viewerHeight: 400

## file: app.py

import jinja2
from shiny import *
import pandas as pd
from pathlib import Path

app_ui = ui.page_fluid(
    ui.output_table(id="table"),
)

def server(input, output, session):
    @output
    @render.table
    def table():
        infile = Path(__file__).parent / "example_table.csv"
        df = pd.read_csv(infile)
        return df

app = App(app_ui, server)

## file: example_table.csv
name,x,y
foo,1,1
bar,2,3
waldo,2
fred,4
plugh,5
thud,7

## file: requirements.txt
Jinja2


It's definitely an incredibly ugly solution (specially because the `csv` is way bigger for most real use-cases), but if you need a hacky-working solution, this does it...
JoeHasell commented 1 year ago

Oh wow! Thanks for thinking outside the box. I guess for smaller datasets, it's also best to just pull it in with an HTTP request. It was because the data is pretty large that I was thinking of reading it locally (and hence is also not very compatible with the work around you are suggesting). Thanks again for you help though!

AlFontal commented 1 year ago

Indeed, it's just an ugly patch so far hahaha

In any case, I did try the HTTP request solution beforehand, but couldn't make it work since shinylive runs on pyodide under the hood, and since pyodide runs completely on the client's browser, it is very limited on the external requests it can make (and it doesn't handle sockets so far). I am way out of depth here on my very limited networking knowledge but you can read pyodide's docs on the issue.

You can't run a simple pd.read_csv(url) since it probably uses either requests or urllib under the hood which are not supported... but I suspect some solution using pyodide's API might exist.

AlFontal commented 1 year ago

Indeed, turns out it was way easier than that, you can do HTTP requests but using pyodide.http.open_url, so just do:

from pyodide.http import open_url

df = pd.read_csv(open_url('url_to_your_table.csv'))

That seems to be working with no issues on my end.

wch commented 1 year ago

I believe the Quarto team is planning on making it easier to embed multiple files in a code chunk, but I don't know the exact plans and roadmap.

gcgbarbosa commented 1 month ago

Is this still an issue? Have we found a solution for this? (besides just uploading the file somewhere)

georgestagg commented 1 month ago

There are now a couple of ways to handle external data for a Shinylive app in Quarto. Either of the two following methods can be used:

1) Include the data as part of additional resources in your Quarto document's YAML header, then load the data from the static web server using Pyodide's open_url(). For example, if there is the file mtcars.csv in the directory data/:

---
title: Shinylive in Quarto
format: html
filters:
  - shinylive
resources:
  - data/mtcars.csv
---

:::{.column-screen}

```{shinylive-python}
#| standalone: true
#| viewerHeight: 600
from shiny import *
from pyodide.http import open_url
import pandas as pd

app_ui = ui.page_fluid(
    ui.output_table(id="table"),
)

def server(input, output, session):
    @output
    @render.table
    def table():
        df = pd.read_csv(open_url('/data/mtcars.csv'))
        return df

app = App(app_ui, server)

:::



2) Or, use Garrick's `quarto-base64` extension to include the additional content directly in your Shinylive blocks, embedded as a base64 encoded binary file: [Further documentation](https://pkg.garrickadenbuie.com/quarto-base64/).