Use SupaHost with Python
Use supabase-py with a SupaHost project. Install the client, query and insert rows, sign users in, run admin scripts with the service_role key, verify users in FastAPI and use the async client.
supabase-py is the official Python client. It talks to your project over HTTPS through the same Data API, Auth, Storage and Functions endpoints the JavaScript client uses, so it works with SupaHost with no changes and no database connection. That matters on SupaHost, where Postgres isn't reachable from outside the instance except through the Business plan's SSH tunnel.
The examples use the recipes table from the quickstart.
Install
python -m venv .venv
source .venv/bin/activate
pip install supabase
Add your project keys
Take the Project URL and keys from the project's Connect tab (subtab App) and export them:
export SUPABASE_URL=https://app-one.acme.supahost.space
export SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...
# Only for trusted server code and scripts:
export SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIs...
Use the anon key for anything that acts on behalf of a user. The service_role key bypasses Row Level Security; keep it in server-side secrets.
Query and insert
import os
from supabase import create_client
supabase = create_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_ANON_KEY"])
response = (
supabase.table("recipes")
.select("id, title, minutes")
.lte("minutes", 30)
.order("created_at", desc=True)
.execute()
)
for recipe in response.data:
print(recipe["title"], recipe["minutes"])
Failed requests raise an exception carrying Postgres's error code and message:
from postgrest.exceptions import APIError
try:
supabase.table("recipes").insert({"title": "Lemon rice", "minutes": 30}).execute()
except APIError as err:
print(err.code, err.message) # 42501 for a Row Level Security violation
This insert fails while signed out, because the quickstart's insert policy is for authenticated users.
Sign in
supabase.auth.sign_up({"email": "[email protected]", "password": "a-long-password"})
session = supabase.auth.sign_in_with_password(
{"email": "[email protected]", "password": "a-long-password"}
)
print(session.user.id)
# Requests on this client now carry the user's token.
supabase.table("recipes").insert({"title": "Lemon rice", "minutes": 30}).execute()
supabase.auth.sign_out()
Admin scripts with the service role
For back-office jobs, such as creating users or fixing data, create a separate client with the service_role key. It skips Row Level Security and can call the Auth admin API:
import os
from supabase import create_client
admin = create_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_SERVICE_ROLE_KEY"])
admin.auth.admin.create_user(
{"email": "[email protected]", "password": "a-long-password", "email_confirm": True}
)
old = admin.table("recipes").delete().lt("created_at", "2024-01-01").execute()
print(f"removed {len(old.data)} rows")
Verify users in an API (FastAPI)
When your frontend calls a Python API, send the user's access token in the Authorization header. The API checks it with Auth and then queries as that user, so policies still apply:
import os
from fastapi import FastAPI, Header, HTTPException
from supabase import create_client, ClientOptions
URL = os.environ["SUPABASE_URL"]
ANON = os.environ["SUPABASE_ANON_KEY"]
app = FastAPI()
@app.get("/my-recipes")
def my_recipes(authorization: str = Header(...)):
token = authorization.removeprefix("Bearer ").strip()
client = create_client(
URL, ANON, options=ClientOptions(auto_refresh_token=False, persist_session=False)
)
try:
user = client.auth.get_user(token).user
except Exception:
raise HTTPException(status_code=401, detail="invalid token")
client.postgrest.auth(token) # run queries as this user
rows = client.table("recipes").select("id, title").eq("user_id", user.id).execute()
return rows.data
Create a client per request, as above. A shared client would mix up users.
The async client
For asyncio apps, use acreate_client:
import asyncio, os
from supabase import acreate_client
async def main():
supabase = await acreate_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_ANON_KEY"])
res = await supabase.table("recipes").select("id, title").limit(5).execute()
print(res.data)
asyncio.run(main())
Edge Functions and Storage
The same client calls your other services:
result = supabase.functions.invoke("estimate-cost", invoke_options={"body": {"recipe_id": 1}})
files = supabase.storage.from_("photos").list()
SQLAlchemy, Django and psycopg
ORMs and drivers that open a Postgres connection need direct database access. On SupaHost that's only available on Business and Enterprise, through the SSH tunnel, and it's meant for tools on a developer machine, such as running migrations. For application code on other plans, use supabase-py as shown here. See Using SQLAlchemy with Supabase Postgres.
Next steps
- Querying and filtering data
- Calling database functions (RPC) with
supabase.rpc() - Managing users (admin API)
- Use SupaHost from a Node.js server for the same patterns in JavaScript