Work with DataFrames
A Store (cf Sharing state) can hold lists and dictionaries, but what if your data is a pandas DataFrame, or a Spark query over millions of rows ? Converting everything into a list of dicts and sending it to the browser would be expensive.
Put a pret.DataFrame in the Store instead. Widgets then request the rows they display, filtering and sorting run where the rows live, and edits are checked row by row. What you can do with it depends on where the rows come from
| You add | Rows live in | Editable ? |
|---|---|---|
| Records, pandas or Arrow | A SQLite copy owned by the Store | Yes |
| A Spark or Ibis query | The original source | No, it is a read-only view of the query |
A query result saved with save_store | The SQLite file of the saved Store | Yes |
A small editable table
Run this notebook with Pret and Metanno installed, Metanno supplies the table widget.
import pandas as pd
from pret import DataFrame, create_store
todos = pd.DataFrame(
[
{"todo_id": 1, "text": "My first todo", "done": True},
{"todo_id": 2, "text": "My second todo", "done": False},
{"todo_id": 3, "text": "My third todo", "done": False},
]
)
store = create_store({"todos": DataFrame(todos)}, sync=True)
sync=True connects browser edits to this Python session.
# Documentation pages have no Python kernel, they show the table below with a browser only Store
store = create_store({"todos": DataFrame(todos)})
from metanno import DataWidgetFactory
factory = DataWidgetFactory(store)
factory.create_table_widget(
store_key="todos",
primary_key="todo_id",
fields=[
{"key": "todo_id", "kind": "text"},
{"key": "text", "kind": "text", "filterable": True},
{"key": "done", "kind": "boolean", "editable": True},
],
style={"height": "200px"},
)
Check a box, then read the rows from Python
store["todos"].materialize().to_pandas()
| todo_id | text | done | |
|---|---|---|---|
| 0 | 1 | My first todo | True |
| 1 | 2 | My second todo | False |
| 2 | 3 | My third todo | False |
materialize() collects the rows as an Arrow table, to_pandas() converts it. Now finish the second todo from Python
store["todos"].update(identity={"todo_id": 2}, values={"done": True})
store["todos"].materialize().to_pandas()
| todo_id | text | done | |
|---|---|---|---|
| 0 | 1 | My first todo | True |
| 1 | 2 | My second todo | True |
| 2 | 3 | My third todo | False |
The checkbox of the second todo changes in the table. identity names the row by a column value, so sorting or filtering the table does not change which row is edited.
Where did the edits go ?
print(todos["done"].tolist())
print(store["todos"].materialize()["done"].to_pylist())
[True, False, False]
[True, True, False]
The pandas table is unchanged. When you added it, Pret copied its rows into a SQLite database owned by the Store, and edits change that copy. SQLite is a database stored in a single file, here kept in memory, so the edits last as long as the Store. Save the Store to keep them in a file.
Browse a query without copying it
A Spark DataFrame or an Ibis table describes a query, its result can be too large to collect. Pret keeps it connected to its source: widgets send their filters and sorts to the source and receive a preview of at most 500 rows. This Ibis example needs pip install 'ibis-framework[duckdb]'
import ibis
backend = ibis.duckdb.connect()
backend.create_table("todos", todos)
query = backend.table("todos")
external = create_store({"todos": DataFrame(query)}, sync=True)
DataWidgetFactory(external).create_table_widget(
store_key="todos",
primary_key="todo_id",
fields=[
{"key": "todo_id", "kind": "text"},
{"key": "text", "kind": "text", "filterable": True},
],
source_requery=True,
style={"height": "200px"},
)
DataFrame(spark_df) works the same way. These query DataFrames are read-only, the source database is never modified.
Make an editable copy of a query
Select what you want to work on, then save it
from pret import save_store
unfinished = query.filter(~query.done)
preview = create_store({"todos": DataFrame(unfinished)}, sync=True)
saved = save_store(preview, "unfinished-todos.pret", bounds={"todos": {"max_rows": 100}})
saved["todos"].update(identity={"todo_id": 3}, values={"done": True})
saved["todos"].materialize().to_pandas()
| todo_id | text | done | |
|---|---|---|---|
| 0 | 2 | My second todo | False |
| 1 | 3 | My third todo | True |
save_store runs unfinished, copies its rows into unfinished-todos.pret and returns a Store connected to that file, where they are editable. max_rows rejects a larger result instead of truncating it, filter the query to choose the rows.
How much data reaches the browser ?
A connected Store sends pages of rows as the table scrolls, after filtering and sorting them where they live. Lists and dictionaries are sent whole, they remain the simplest choice for small collections and application values.
Next, save and reopen the Store. To use DataFrames in your own components, or connect another kind of source, see How widgets query DataFrames.