Skip to content

Commit

Permalink
v0.1.0: 🎉
Browse files Browse the repository at this point in the history
  • Loading branch information
SiddhantSadangi committed Aug 3, 2023
1 parent 28c97c0 commit 74b0fa5
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 107 deletions.
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@

A Streamlit connection component to connect Streamlit to Supabase Storage and Database.
## :thinking: Why use this?
- [X] A new `query()` method available to run cached select queries on the database. **Save time and money** on your API requests
- [X] **Same method names as the Supabase Python API**
- [X] It is built on top of [`storage-py`](https://github.com/supabase-community/storage-py) and **exposes more methods** than currently supported by the Supabase Python API. For example, `update()`, `create_signed_upload_url()`, and `upload_to_signed_url()`
- [X] **Consistent logging syntax.** All statements follow the syntax `client.method("bucket_id", **options)`
- [X] Cache functionality to cache returned results. **Save time and money** on your API requests
- [X] Same method names as the Supabase Python API. **Minimum relearning required**
- [X] Built on top of [`storage-py`](https://github.com/supabase-community/storage-py) and **exposes more methods** than currently supported by the Supabase Python API. For example, `update()`, `create_signed_upload_url()`, and `upload_to_signed_url()`
- [X] **Less keystrokes required** when integrating with your Streamlit app.

<details open>
<details close>
<summary>Examples with and without the connector </summary>
<br>
<table>
Expand Down Expand Up @@ -133,7 +132,7 @@ pip install st-supabase-connection
```
2. Set the `SUPABASE_URL` and `SUPABASE_KEY` Streamlit secrets as described [here](https://docs.streamlit.io/streamlit-community-cloud/get-started/deploy-an-app/connect-to-data-sources/secrets-management).

> [!NOTE]
> [!INFO]
> For local development outside Streamlit, you can also set these as your environment variables (recommended), or pass these to the `url` and `key` args of `st.experimental_connection()` (not recommended).
## :pen: Usage
Expand All @@ -147,18 +146,19 @@ pip install st-supabase-connection
st_supabase_client = st.experimental_connection(
name="YOUR_CONNECTION_NAME",
type=SupabaseConnection,
ttl=None,
url="YOUR_SUPABASE_URL", # not needed if provided as a streamlit secret
key="YOUR_SUPABASE_KEY", # not needed if provided as a streamlit secret
)
```
3. Happy Streamlit-ing! :balloon:
3. Use in your app to query tables and files. Happy Streamlit-ing! :balloon:

## :writing_hand: Examples
### :package: Storage operations

#### List existing buckets
```python
>>> st_supabase.list_buckets()
>>> st_supabase.list_buckets(ttl=None)
[
SyncBucket(
id="bucket1",
Expand Down Expand Up @@ -206,7 +206,7 @@ pip install st-supabase-connection

#### List objects in a bucket
```python
>>> st_supabase_client.list_objects("new_bucket", path="folder1")
>>> st_supabase_client.list_objects("new_bucket", path="folder1", ttl=0)
[
{
"name": "new_test.png",
Expand Down Expand Up @@ -234,7 +234,7 @@ pip install st-supabase-connection
### :file_cabinet: Database operations
#### Simple query
```python
>>> st_supabase.query("*", from_="countries", ttl=None).execute()
>>> st_supabase.query("*", table="countries", ttl=0).execute()
APIResponse(
data=[
{"id": 1, "name": "Afghanistan"},
Expand All @@ -246,7 +246,7 @@ APIResponse(
```
#### Query with join
```python
>>> st_supabase.query("name, teams(name)", from_="users", count="exact", ttl=None).execute()
>>> st_supabase.query("name, teams(name)", table="users", count="exact", ttl="1h").execute()
APIResponse(
data=[
{"name": "Kiran", "teams": [{"name": "Green"}, {"name": "Blue"}]},
Expand All @@ -257,7 +257,7 @@ APIResponse(
```
#### Filter through foreign tables
```python
>>> st_supabase.query("name, countries(*)", count="exact", from_="cities", ttl=0).eq(
>>> st_supabase.query("name, countries(*)", count="exact", table="cities", ttl=None).eq(
"countries.name", "Curaçao"
).execute()

Expand Down Expand Up @@ -310,7 +310,7 @@ APIResponse(
> [!INFO]
> Check the [Supabase Python API reference](https://supabase.com/docs/reference/python/select) for more examples.
## :star: Explore all options in Streamlit
## :star: Explore all options in a Streamlit app
[![Open in Streamlit](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://st-supabase-connection.streamlit.app/)

## :bow: Acknowledgements
Expand Down
134 changes: 106 additions & 28 deletions demo/app.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import contextlib

import pandas as pd
import streamlit as st

from st_supabase_connection import SupabaseConnection, __version__

VERSION = __version__

st.set_page_config(
page_title="Streamlit SupabaseConnection Demo app",
page_title="st_supabase_connection",
page_icon="🔌",
menu_items={
"About": f"🔌 Streamlit Supabase Connection v{VERSION} "
Expand Down Expand Up @@ -89,10 +90,20 @@
5. The app will construct the statement for that you can copy and use in your own app.
"""
)

if st.button(
"Clear the cache to fetch latest data🧹",
use_container_width=True,
type="primary",
):
st.cache_data.clear()
st.cache_resource.clear()
st.success("Cache cleared")

st.components.v1.html(sidebar_html, height=600)

# ---------- MAIN PAGE ----------
st.header("🔌Streamlit SupabaseConnection Demo")
st.header("🔌Supabase Connection for Streamlit")

st.write("📖 Demo and tutorial for `st_supabase_connection` for Supabase Storage and Database.")

Expand All @@ -102,19 +113,22 @@
demo_tab, custom_tab = st.tabs(["👶Use demo project", "🫅Use your own project"])

with demo_tab:
st.info(
"Limited data and operations",
icon="⚠️",
ttl = st.text_input(
"Connection cache duration",
value="",
placeholder="Optional",
help="This does not affect results caching. Leave blank to cache indefinitely.",
)
ttl = None if ttl == "" else ttl

if st.button(
"Initialize client ⚡",
type="primary",
use_container_width=True,
):
try:
st.session_state["client"] = st.experimental_connection(
name="supabase_connection",
type=SupabaseConnection,
name="supabase_connection", type=SupabaseConnection, ttl=ttl
)
st.session_state["initialized"] = True
st.session_state["project"] = "demo"
Expand All @@ -128,17 +142,25 @@

st.write("A connection is initialized as")
st.code(
"""
f"""
st_supabase = st.experimental_connection(
name="supabase_connection", type=SupabaseConnection
name="supabase_connection", type=SupabaseConnection, {ttl=}
)
""",
language="python",
)

with custom_tab:
with st.form(key="credentials"):
url = st.text_input("Enter Supabase URL")
lcol, rcol = st.columns([2, 1])
url = lcol.text_input("Enter Supabase URL")
ttl = rcol.text_input(
"Connection cache duration",
value="",
placeholder="Optional",
help="This does not affect results caching. Leave blank to cache indefinitely",
)
ttl = None if ttl == "" else ttl
key = st.text_input("Enter Supabase key", type="password")

if st.form_submit_button(
Expand All @@ -150,6 +172,7 @@
st.session_state["client"] = st.experimental_connection(
name="supabase_connection",
type=SupabaseConnection,
ttl=ttl,
url=url,
key=key,
)
Expand All @@ -165,10 +188,14 @@

st.write("A connection is initialized as")
st.code(
"""
f"""
st_supabase = st.experimental_connection(
name="supabase_connection", type=SupabaseConnection, url=url, key=key
)
name="supabase_connection",
type=SupabaseConnection,
{ttl=},
url=url,
key=key,
)
""",
language="python",
)
Expand Down Expand Up @@ -217,12 +244,23 @@

bucket_id = rcol.text_input(
"Enter the bucket id",
placeholder="my_bucket" if operation != "update_bucket" else "",
placeholder="Required" if operation != "list_buckets" else "",
disabled=operation == "list_buckets",
help="The unique identifier for the bucket",
)

if operation in ["delete_bucket", "empty_bucket", "get_bucket"]:
if operation == "get_bucket":
ttl = st.text_input(
"Results cache duration",
value="",
placeholder="Optional",
help="This does not affect results caching. Leave blank to cache indefinitely",
)
ttl = None if ttl == "" else ttl
constructed_storage_query = f"""st_supabase.{operation}("{bucket_id}", {ttl=})"""
st.session_state["storage_disabled"] = False if bucket_id else True

elif operation in ["delete_bucket", "empty_bucket"]:
constructed_storage_query = f"""st_supabase.{operation}("{bucket_id}")"""
st.session_state["storage_disabled"] = False if bucket_id else True

Expand Down Expand Up @@ -314,17 +352,32 @@
"""

elif operation == "list_buckets":
constructed_storage_query = f"""st_supabase.{operation}()"""
ttl = st.text_input(
"Results cache duration",
value="",
placeholder="Optional",
help="This does not affect results caching. Leave blank to cache indefinitely",
)
ttl = None if ttl == "" else ttl
constructed_storage_query = f"""st_supabase.{operation}({ttl=})"""
st.session_state["storage_disabled"] = False

elif operation == "download":
source_path = st.text_input(
lcol, rcol = st.columns([3, 1])
source_path = lcol.text_input(
"Enter source path in the bucket",
placeholder="/folder/subFolder/file.txt",
)
ttl = rcol.text_input(
"Results cache duration",
value="",
placeholder="Optional",
help="This does not affect results caching. Leave blank to cache indefinitely",
)
ttl = None if ttl == "" else ttl

constructed_storage_query = (
f"""st_supabase.{operation}("{bucket_id}", {source_path=})"""
f"""st_supabase.{operation}("{bucket_id}", {source_path=}, {ttl=})"""
)
st.session_state["storage_disabled"] = False if all([bucket_id, source_path]) else True

Expand Down Expand Up @@ -355,10 +408,18 @@

st.session_state["storage_disabled"] = False if all([bucket_id, paths]) else True
elif operation == "list_objects":
path = st.text_input(
lcol, rcol = st.columns([3, 1])
path = lcol.text_input(
"Enter the folder path to list objects from",
placeholder="/folder/subFolder/",
)
ttl = rcol.text_input(
"Results cache duration",
value="",
placeholder="Optional",
help="This does not affect results caching. Leave blank to cache indefinitely",
)
ttl = None if ttl == "" else ttl

col1, col2, col3, col4 = st.columns(4)

Expand Down Expand Up @@ -387,17 +448,27 @@
horizontal=True,
)

constructed_storage_query = f"""st_supabase.{operation}("{bucket_id}", {path=}, {limit=}, {offset=}, {sortby=}, {order=})"""
constructed_storage_query = f"""st_supabase.{operation}("{bucket_id}", {path=}, {limit=}, {offset=}, {sortby=}, {order=}, {ttl=})"""

st.session_state["storage_disabled"] = False if bucket_id else True

elif operation == "get_public_url":
filepath = st.text_input(
lcol, rcol = st.columns([3, 1])
filepath = lcol.text_input(
"Enter the path to file",
placeholder="/folder/subFolder/image.jpg",
)
ttl = rcol.text_input(
"Results cache duration",
value="",
placeholder="Optional",
help="This does not affect results caching. Leave blank to cache indefinitely",
)
ttl = None if ttl == "" else ttl

constructed_storage_query = f"""st_supabase.get_public_url("{bucket_id}",{filepath=})"""
constructed_storage_query = (
f"""st_supabase.get_public_url("{bucket_id}",{filepath=}, {ttl=})"""
)
st.session_state["storage_disabled"] = False if all([bucket_id, filepath]) else True

elif operation == "create_signed_urls":
Expand Down Expand Up @@ -538,7 +609,8 @@
st.write(response)
elif operation == "list_objects":
st.info(f"Listing **{len(response)}** objects")
st.write(response)
_df = pd.DataFrame.from_dict(response)
st.dataframe(_df, use_container_width=True)
elif operation == "get_public_url":
st.success(response, icon="🔗")
elif operation == "create_signed_urls":
Expand Down Expand Up @@ -659,12 +731,12 @@
request_builder_query_label = "Enter the columns to fetch as comma-separated strings"
placeholder = value = "*"
ttl = rcol_placeholder.text_input(
"Enter cache expiry duration",
"Result cache duration",
value=0,
placeholder=None,
help="Set as `0` to always fetch the latest results, and leave blank to cache indefinitely.",
)
ttl = None if ttl == "" else ttl
placeholder = value = "*"
elif request_builder == "delete":
request_builder_query_label = "Delete query"
placeholder = value = "Delete does not take a request builder query"
Expand Down Expand Up @@ -715,14 +787,18 @@

operators = operators.replace(".__init__()", "").replace(".execute()", "")

ttl = None if ttl == "" else ttl

if operators:
if request_builder == "select":
constructed_db_query = f"""st_supabase.query({request_builder_query}, from_="{table}", {ttl=}){operators}.execute()"""
constructed_db_query = f"""st_supabase.query({request_builder_query}, {table=}, {ttl=}){operators}.execute()"""
else:
constructed_db_query = f"""st_supabase.table("{table}").{request_builder}({request_builder_query}){operators}.execute()"""
else:
if request_builder == "select":
constructed_db_query = f"""st_supabase.query({request_builder_query}, from_="{table}", {ttl=}).execute()"""
constructed_db_query = (
f"""st_supabase.query({request_builder_query}, {table=}, {ttl=}).execute()"""
)
else:
constructed_db_query = f"""st_supabase.table("{table}").{request_builder}({request_builder_query}).execute()"""
st.write("**Constructed statement**")
Expand Down Expand Up @@ -751,7 +827,9 @@
data, count = eval(constructed_db_query)

if count_method:
st.write(f"{count[-1]} rows {request_builder}ed")
st.write(
f"**{count[-1]}** rows {request_builder}ed. `count` does not take `limit` into account."
)
if view == "Dataframe":
st.dataframe(data[-1], use_container_width=True)
else:
Expand Down
Loading

0 comments on commit 74b0fa5

Please sign in to comment.