Skip to content

Commit 8c0a3c5

Browse files
Data Docs (#320)
* Adding pages * Updates so far * Updates * Updates * additional updates * Current draft * Current * Updates after chat * Updates * Notes plus remove old modules * More updates * Updates made * Updated with apps * Overhaul reading data * Updates to persistent storage * Updated content * additional context * Progress * both ibis examples added * More updates * Updates * Connect info * Added link * Switching order * Correction * Added notif * Simplified * wip updates to persistent data article * Added reading from remote * finish brain dump on persistent data * Remove link in Essentials section * Small edits * Corrections to first example * Corrected GoogleSheets example * Smoothed out the string/boolean thing * Restoring paste error in setup for sheets * Removed try except at start * Updates to dotenv * Update docs/reading-data.qmd * Minor updates to wording * small changes/improvements * QA on code up to cloud store * More corrections to reading data * More correcdtions to reaction section * Final corrections for ibis * small changes/improvements * Update docs/persistent-storage.qmd * Updating s3 * Moving some examples to separate app folder, add screenshot * Changing examples * Minor grammar edits to reading data * Minor grammar edits for persistent storage * Updating template pages --------- Co-authored-by: Carson <[email protected]>
1 parent 15c98ee commit 8c0a3c5

File tree

14 files changed

+787
-3
lines changed

14 files changed

+787
-3
lines changed

_quarto.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ website:
271271
- docs/reactive-foundations.qmd
272272
- docs/reactive-patterns.qmd
273273
- docs/reactive-mutable.qmd
274+
- section: "<span class='emoji-icon'>🗃️</span> __Data__"
275+
contents:
276+
- docs/reading-data.qmd
277+
- docs/persistent-storage.qmd
274278
- section: "<span class='emoji-icon'>📝</span> __Syntax modes__"
275279
contents:
276280
- docs/express-vs-core.qmd
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import polars as pl
2+
from setup import append_info, load_data, save_info
3+
from shiny import reactive
4+
from shiny.express import app_opts, input, render, ui
5+
6+
with ui.sidebar():
7+
ui.input_text("name_input", "Enter your name", placeholder="Your name here")
8+
ui.input_checkbox("checkbox", "I like checkboxes")
9+
ui.input_slider("slider", "My favorite number is:", min=0, max=100, value=50)
10+
ui.input_action_button("submit_button", "Submit")
11+
12+
# Load the initial data into a reactive value when the app starts
13+
data = reactive.value(load_data())
14+
15+
16+
# Append new user data on submit
17+
@reactive.effect
18+
@reactive.event(input.submit_button)
19+
def submit_data():
20+
info = {
21+
"name": input.name_input(),
22+
"checkbox": input.checkbox(),
23+
"favorite_number": input.slider(),
24+
}
25+
# Update the (in-memory) data
26+
d = data()
27+
data.set(append_info(d, info))
28+
# Save info to persistent storage (out-of-memory)
29+
save_info(info)
30+
# Provide some user feedback
31+
ui.notification_show("Submitted, thanks!")
32+
33+
34+
# Data grid that shows the current data
35+
@render.data_frame
36+
def show_results():
37+
return render.DataGrid(data())
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import gspread
2+
import polars as pl
3+
4+
# Authenticate with Google Sheets using a service account
5+
gc = gspread.service_account(filename="service_account.json")
6+
7+
# Put your URL here
8+
sheet = gc.open_by_url("https://docs.google.com/spreadsheets/d/your_workbook_id")
9+
WORKSHEET = sheet.get_worksheet(0)
10+
11+
import polars as pl
12+
13+
# A polars schema that the data should conform to
14+
SCHEMA = {"name": pl.Utf8, "checkbox": pl.String, "favorite_number": pl.Int32}
15+
16+
17+
def load_data():
18+
return pl.from_dicts(
19+
WORKSHEET.get_all_records(expected_headers=SCHEMA.keys()), schema=SCHEMA
20+
)
21+
22+
23+
def save_info(info: dict):
24+
# Google Sheets expects a list of values for the new row
25+
new_row = list(info.values())
26+
WORKSHEET.append_row(new_row, insert_data_option="INSERT_ROWS")
27+
28+
29+
def append_info(d: pl.DataFrame, info: dict):
30+
# Cast the boolean to a string for storage
31+
info["checkbox"] = str(info["checkbox"])
32+
return pl.concat([d, pl.DataFrame(info, schema=SCHEMA)], how="vertical")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import ibis
2+
import polars as pl
3+
4+
# NOTE: app.py should import CONN and close it via
5+
# `_ = session.on_close(CONN.disconnect)` or similar
6+
CONN = ibis.postgres.connect(
7+
user="postgres", password="", host="localhost", port=5432, database="template1"
8+
)
9+
TABLE_NAME = "testapp"
10+
11+
SCHEMA = {"name": pl.Utf8, "checkbox": pl.Boolean, "favorite_number": pl.Int32}
12+
13+
14+
def load_data():
15+
return CONN.table(TABLE_NAME).to_polars()
16+
17+
18+
def save_info(info: dict):
19+
new_row = pl.DataFrame(info, schema=SCHEMA)
20+
CONN.insert(TABLE_NAME, new_row, overwrite=False)
21+
22+
23+
def append_info(d: pl.DataFrame, info: dict):
24+
return pl.concat([d, pl.DataFrame(info, schema=SCHEMA)], how="vertical")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import polars as pl
2+
3+
URI = "postgresql://postgres@localhost:5432/template1"
4+
TABLE_NAME = "testapp"
5+
6+
SCHEMA = {"name": pl.Utf8, "checkbox": pl.Boolean, "favorite_number": pl.Int32}
7+
8+
9+
def load_data():
10+
return pl.read_database_uri(f"SELECT * FROM {TABLE_NAME}", URI)
11+
12+
13+
def save_info(info: dict):
14+
new_row = pl.DataFrame(info, schema=SCHEMA)
15+
new_row.write_database(TABLE_NAME, URI, if_table_exists="append")
16+
17+
18+
def append_info(d: pl.DataFrame, info: dict):
19+
return pl.concat([d, pl.DataFrame(info, schema=SCHEMA)], how="vertical")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from datetime import datetime
2+
3+
import polars as pl
4+
5+
DATA_BUCKET = "s3://my-bucket/data/"
6+
STORAGE_OPTIONS = {
7+
"aws_access_key_id": "<secret>",
8+
"aws_secret_access_key": "<secret>",
9+
"aws_region": "us-east-1",
10+
}
11+
12+
SCHEMA = {
13+
"name": pl.Utf8,
14+
"checkbox": pl.String,
15+
"favorite_number": pl.Int32,
16+
"date": pl.Datetime,
17+
}
18+
19+
20+
def load_data():
21+
return pl.read_parquet(
22+
f"{DATA_BUCKET}**/*.parquet", storage_options=STORAGE_OPTIONS
23+
)
24+
25+
26+
def save_info(info: dict):
27+
info["date"] = datetime.now()
28+
new_row = pl.DataFrame(info, schema=SCHEMA)
29+
new_row.write_parquet(
30+
f"{DATA_BUCKET}", partition_by="date", storage_options=STORAGE_OPTIONS
31+
)
32+
33+
34+
def append_info(d: pl.DataFrame, info: dict):
35+
info["date"] = datetime.now()
36+
return pl.concat([d, pl.DataFrame(info, schema=SCHEMA)], how="vertical")
103 KB
Loading

docs/apps/reading-data/ibis-app.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import ibis
2+
from ibis import _
3+
from shiny.express import ui, render, input, session
4+
from shiny import reactive
5+
6+
# Connect to the database (quick, doesn't load data)
7+
con = ibis.postgres.connect(
8+
user="", password="", host="", port=, database=""
9+
)
10+
dat = con.table("weather")
11+
end_session = session.on_ended(con.disconnect)
12+
13+
with ui.sidebar():
14+
ui.input_checkbox_group(
15+
"season",
16+
"Season",
17+
choices=["Summer", "Winter", "Autumn", "Spring"],
18+
selected="Summer",
19+
)
20+
# Import just the unique city names for our selectize input
21+
cities = dat.select("city_name").distinct().execute()["city_name"].to_list()
22+
ui.input_selectize("city", "City", choices=cities)
23+
24+
25+
# Store data manipulations in a reactive calculation
26+
# (convenient when using the data in multiple places)
27+
@reactive.calc
28+
def filtered_dat():
29+
return dat.filter(
30+
[_.city_name == input.city(), _.season.isin(input.season())]
31+
)
32+
33+
# Display the filtered data
34+
@render.data_frame
35+
def results_df():
36+
return filtered_dat().execute()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import polars as pl
2+
from shiny import reactive
3+
from shiny.express import input, render, ui
4+
5+
# Use `scan_*` instead of `read_*` to use the lazy API
6+
dat = pl.scan_parquet("./daily_weather.parquet")
7+
8+
with ui.sidebar():
9+
ui.input_checkbox_group(
10+
"season",
11+
"Season",
12+
choices=["Summer", "Winter", "Fall", "Spring"],
13+
selected="Summer",
14+
)
15+
# Import just the unique city names for our selectize input
16+
cities = dat.select("city_name").unique().collect().to_series().to_list()
17+
ui.input_selectize("city", "City", choices=cities)
18+
19+
20+
# Store manipulation in a reactive calc
21+
# (convenient for writing once and using in multiple places)
22+
@reactive.calc
23+
def filtered_dat():
24+
return dat.filter(pl.col("city_name") == input.city()).filter(
25+
pl.col("season").is_in(input.season())
26+
)
27+
28+
29+
# Display the filtered data
30+
@render.data_frame
31+
def results_df():
32+
return filtered_dat().collect()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import ibis
2+
from shiny.express import render
3+
from shiny import reactive
4+
5+
con = ibis.postgres.connect(user="", password="", host="", port=, database="")
6+
table = con.table("tablename")
7+
8+
def check_last_updated():
9+
return table.last_updated.max().execute()
10+
11+
# Every 5 seconds, check if the max timestamp has changed
12+
@reactive.poll(check_last_updated, interval_secs=5)
13+
def data():
14+
return table.execute()
15+
16+
@render.data_frame
17+
def result():
18+
return data()

0 commit comments

Comments
 (0)