A Streamlit connection component to connect Streamlit to Supabase Storage, Database, and Auth.
- π Streamlit Supabase Connector
- Contents
- π Quickstart
- π§βπ Interactive tutorial
- π€ Why use this?
- π οΈ Setup
- πͺ Usage
- π Supported methods
- π Examples
- β Explore all options in a demo app
- π Acknowledgements
- π€ Want to support my work?
-
Install the connector and its dependencies.
pip install st-supabase-connection
-
Provide your Supabase credentials. In Streamlit Cloud, add them to
secrets.toml:[connections.supabase_connection] url = "https://your-project.supabase.co" key = "service_role_or_anon_key"
For local development you can use environment variables (
SUPABASE_URL,SUPABASE_KEY) or pass the values directly during connection creation. -
Create the cached connection in your app.
import streamlit as st from st_supabase_connection import SupabaseConnection st_supabase = st.connection( name="supabase_connection", type=SupabaseConnection, ttl=None, # cache indefinitely; override when you need fresher data ) # Example: list buckets without re-authenticating on every rerun buckets = st_supabase.list_buckets() st.write(buckets)
- Cache functionality to cache returned results. Save time and money on your API requests
- Same method names as the Supabase Python API. Minimum relearning required
- Exposes more storage methods than currently supported by the Supabase Python API. For example,
update(),create_signed_upload_url(), andupload_to_signed_url() - Handles common Supabase quirksβleading slashes are normalised, MIME types inferred, and downloads streamed in memoryβso you spend less time on glue code.
- Less keystrokes required when integrating with your Streamlit app.
Examples with and without the connector
| Without connector | With connector |
| Download file to local system from Supabase storage | |
import mimetypes
import streamlit as st
from supabase import create_client
supabase_client = create_client(
supabase_url="...", supabase_key="..."
)
bucket_id = st.text_input("Enter the bucket_id")
source_path = st.text_input("Enter source path")
file_name = source_path.split("/")[-1]
if st.button("Request download"):
with open(file_name, "wb+") as f:
response = supabase_client.storage.from_(
bucket_id
).download(source_path)
f.write(response)
mime = mimetypes.guess_type(file_name)[0]
data = open(file_name, "rb")
st.download_button(
"Download file", data=data,
file_name=file_name, mime=mime,
) |
import streamlit as st
from st_supabase_connection import SupabaseConnection
st_supabase_client = st.connection(
name="supabase_connection", type=SupabaseConnection
)
bucket_id = st.text_input("Enter the bucket_id")
source_path = st.text_input("Enter source path")
if st.button("Request download"):
file_name, mime, data = st_supabase_client.download(
bucket_id,
source_path,
) # returns (name, mime_type, bytes)
st.download_button(
"Download file", data=data,
file_name=file_name, mime=mime,
) |
| Upload file from local system to Supabase storage | |
import streamlit as st
from supabase import create_client
supabase_client = create_client(
supabase_key="...", supabase_url="..."
)
bucket_id = st.text_input("Enter the bucket_id")
uploaded_file = st.file_uploader("Choose a file")
destination_path = st.text_input("Enter destination path")
overwrite = "true" if st.checkbox("Overwrite?") else "false"
with open(uploaded_file.name, "wb") as f:
f.write(uploaded_file.getbuffer())
if st.button("Upload"):
with open(uploaded_file.name, "rb") as f:
supabase_client.storage.from_(bucket_id).upload(
path=destination_path,
file=f,
file_options={
"content-type": uploaded_file.type,
"x-upsert": overwrite,
},
) |
import streamlit as st
from st_supabase_connection import SupabaseConnection
st_supabase_client = st.connection(
name="supabase_connection", type=SupabaseConnection
)
bucket_id = st.text_input("Enter the bucket_id")
uploaded_file = st.file_uploader("Choose a file")
destination_path = st.text_input("Enter destination path")
overwrite = "true" if st.checkbox("Overwrite?") else "false"
if st.button("Upload"):
st_supabase_client.upload(
bucket_id, "local", uploaded_file,
destination_path, overwrite,
) |
- Install
st-supabase-connection
pip install st-supabase-connection- Set the
SUPABASE_URLandSUPABASE_KEYStreamlit secrets as described here.
Note
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.connection().
- Import
from st_supabase_connection import SupabaseConnection, execute_query- Initialize
st_supabase_client = st.connection(
name="YOUR_CONNECTION_NAME",
type=SupabaseConnection,
ttl=None,
)-
Use the connection to work with Storage, Database, and Auth in a cached, Streamlit-friendly way:
# Storage file_name, mime, data = st_supabase.download("bucket", "path/to/report.csv", ttl=300) # Database (leverages postgrest-py under the hood) from st_supabase_connection import execute_query users = execute_query( st_supabase.table("users").select("name, email").order("created_at", desc=True), ttl="15m", ) # Auth (cached helper) st_supabase.cached_sign_in_with_password({"email": email, "password": password})
Storage
-
delete_bucket() -
empty_bucket() -
get_bucket() -
list_buckets() -
create_bucket() -
upload() -
download() -
update_bucket() -
move() -
list_objects() -
create_signed_urls() -
get_public_url() -
create_signed_upload_url() -
upload_to_signed_url()
Database
-
execute_query()- Executes the passed query with caching enabled. - All methods supported by postgrest-py.
Auth
-
cached_sign_in_with_password()- Cached version ofsign_in_with_password()for faster sign-in. - All methods supported by Supabase's Python API .
>>> st_supabase_client.list_buckets(ttl=None)
[
SyncBucket(
id="bucket1",
name="bucket1",
owner="",
public=False,
created_at=datetime.datetime(2023, 7, 31, 19, 56, 21, 518438, tzinfo=tzutc()),
updated_at=datetime.datetime(2023, 7, 31, 19, 56, 21, 518438, tzinfo=tzutc()),
file_size_limit=None,
allowed_mime_types=None,
),
SyncBucket(
id="bucket2",
name="bucket2",
owner="",
public=True,
created_at=datetime.datetime(2023, 7, 31, 19, 56, 28, 203536, tzinfo=tzutc()),
updated_at=datetime.datetime(2023, 7, 31, 19, 56, 28, 203536, tzinfo=tzutc()),
file_size_limit=100,
allowed_mime_types=["image/jpg", "image/png"],
),
]>>> st_supabase_client.create_bucket("new_bucket")
{'name': 'new_bucket'}>>> st_supabase_client.get_bucket("new_bucket")
SyncBucket(id='new_bucket', name='new_bucket', owner='', public=True, created_at=datetime.datetime(2023, 8, 2, 19, 41, 44, 810000, tzinfo=tzutc()), updated_at=datetime.datetime(2023, 8, 2, 19, 41, 44, 810000, tzinfo=tzutc()), file_size_limit=None, allowed_mime_types=None)>>> st_supabase_client.update_bucket(
"new_bucket",
file_size_limit=100,
allowed_mime_types=["image/jpg", "image/png"],
public=True,
)
{'message': 'Successfully updated'}>>> st_supabase_client.move("new_bucket", "test.png", "folder1/new_test.png")
{'message': 'Successfully moved'}>>> st_supabase_client.list_objects("new_bucket", path="folder1", ttl=0)
[
{
"name": "new_test.png",
"id": "e506920e-2834-440e-85f1-1d5476927582",
"updated_at": "2023-08-02T19:53:22.53986+00:00",
"created_at": "2023-08-02T19:52:20.404391+00:00",
"last_accessed_at": "2023-08-02T19:53:21.833+00:00",
"metadata": {
"eTag": '"814a0034f5549e957ee61360d87457e5"',
"size": 473831,
"mimetype": "image/png",
"cacheControl": "max-age=3600",
"lastModified": "2023-08-02T19:53:23.000Z",
"contentLength": 473831,
"httpStatusCode": 200,
},
}
]>>> st_supabase_client.empty_bucket("new_bucket")
{'message': 'Successfully emptied'}>>> st_supabase_client.delete_bucket("new_bucket")
{'message': 'Successfully deleted'}>>> execute_query(st_supabase_client.table("countries").select("*"), ttl=0)
APIResponse(
data=[
{"id": 1, "name": "Afghanistan"},
{"id": 2, "name": "Albania"},
{"id": 3, "name": "Algeria"},
],
count=None,
)>>> execute_query(
st_supabase_client.table("users").select("name, teams(name)", count="exact"),
ttl="1h",
)
APIResponse(
data=[
{"name": "Kiran", "teams": [{"name": "Green"}, {"name": "Blue"}]},
{"name": "Evan", "teams": [{"name": "Blue"}]},
],
count=2,
)>>> execute_query(
st_supabase_client.table("cities").select("name, countries(*)", count="exact").eq("countries.name", "CuraΓ§ao"),
ttl=None,
)
APIResponse(
data=[
{
"name": "Kralendijk",
"countries": {
"id": 2,
"name": "CuraΓ§ao",
"iso2": "CW",
"iso3": "CUW",
"local_name": None,
"continent": None,
},
},
{"name": "Willemstad", "countries": None},
],
count=2,
)>>> execute_query(
st_supabase_client.table("countries").insert(
[{"name": "Wakanda", "iso2": "WK"}, {"name": "Wadiya", "iso2": "WD"}], count="None"
),
ttl=0,
)
APIResponse(
data=[
{
"id": 250,
"name": "Wakanda",
"iso2": "WK",
"iso3": None,
"local_name": None,
"continent": None,
},
{
"id": 251,
"name": "Wadiya",
"iso2": "WD",
"iso3": None,
"local_name": None,
"continent": None,
},
],
count=None,
)Note
If the call is valid, all Supabase Auth methods return the same response structure:
{
"user": {
"id": "e1f550fd-9cd1-44e4-bbe4-c04e91cf5544",
"app_metadata": {
"provider": "email",
"providers": ["email"]
},
"user_metadata": {
"attribution": "I made it :)",
"fname": "Siddhant"
},
"aud": "authenticated",
"confirmation_sent_at": null,
"recovery_sent_at": null,
"email_change_sent_at": null,
"new_email": null,
"invited_at": null,
"action_link": null,
"email": "[email protected]",
"phone": "",
"created_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 365359, tzinfo=datetime.timezone.utc)",
"confirmed_at": null,
"email_confirmed_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 373966, tzinfo=datetime.timezone.utc)",
"phone_confirmed_at": null,
"last_sign_in_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 377070, tzinfo=datetime.timezone.utc)",
"role": "authenticated",
"updated_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 381584, tzinfo=datetime.timezone.utc)",
"identities": [
{
"id": "e1f550fd-9cd1-44e4-bbe4-c04e91cf5544",
"user_id": "e1f550fd-9cd1-44e4-bbe4-c04e91cf5544",
"identity_data": {
"email": "[email protected]",
"sub": "e1f550fd-9cd1-44e4-bbe4-c04e91cf5544"
},
"provider": "email",
"created_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 370040, tzinfo=datetime.timezone.utc)",
"last_sign_in_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 370002, tzinfo=datetime.timezone.utc)",
"updated_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 370040, tzinfo=datetime.timezone.utc)"
}
],
"factors": null
},
"session": {
"provider_token": null,
"provider_refresh_token": null,
"access_token": "***",
"refresh_token": "***",
"expires_in": 3600,
"expires_at": 1696800390,
"token_type": "bearer",
"user": {
"id": "e1f550fd-9cd1-44e4-bbe4-c04e91cf5544",
"app_metadata": {
"provider": "email",
"providers": ["email"]
},
"user_metadata": {
"attribution": "I made it :)",
"fname": "Siddhant"
},
"aud": "authenticated",
"confirmation_sent_at": null,
"recovery_sent_at": null,
"email_change_sent_at": null,
"new_email": null,
"invited_at": null,
"action_link": null,
"email": "[email protected]",
"phone": "",
"created_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 365359, tzinfo=datetime.timezone.utc)",
"confirmed_at": null,
"email_confirmed_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 373966, tzinfo=datetime.timezone.utc)",
"phone_confirmed_at": null,
"last_sign_in_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 377070, tzinfo=datetime.timezone.utc)",
"role": "authenticated",
"updated_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 381584, tzinfo=datetime.timezone.utc)",
"identities": [
{
"id": "e1f550fd-9cd1-44e4-bbe4-c04e91cf5544",
"user_id": "e1f550fd-9cd1-44e4-bbe4-c04e91cf5544",
"identity_data": {
"email": "[email protected]",
"sub": "e1f550fd-9cd1-44e4-bbe4-c04e91cf5544"
},
"provider": "email",
"created_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 370040, tzinfo=datetime.timezone.utc)",
"last_sign_in_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 370002, tzinfo=datetime.timezone.utc)",
"updated_at": "datetime.datetime(2023, 10, 8, 20, 26, 30, 370040, tzinfo=datetime.timezone.utc)"
}
],
"factors": null
}
}
}st_supabase_client.auth.sign_up(
dict(
email='[email protected]',
password='***',
options=dict(
data=dict(
fname='Siddhant',
attribution='I made it :)',
)
)
)
)SupabaseConnection() offers a cached version of sign_in_with_password() for faster, request-free sign-ins.
st_supabase_client.cached_sign_in_with_password(dict(email='[email protected]', password='***'))st_supabase_client.auth.get_session()st_supabase_client.auth.get_user()st_supabase_client.auth.sign_out()Note
Check the Supabase Python API reference for more examples.
This connector builds upon the awesome work done by the open-source community in general and the Supabase Community in particular. I cannot be more thankful to all the authors whose work I have used either directly or indirectly.
Thanks to all contributors to this project π
