supabase-py-lite
In-memory Supabase client for testing. Zero infrastructure, drop-in replacement for
supabase-py.
Like Qdrant's client(":memory:"), but for Supabase.
from supabase_py_lite import create_client
supabase_client = create_client(":memory:")
supabase_client.table("users").insert({"id": 1, "name": "Alice"}).execute()
result = supabase_client.table("users").select("*").eq("name", "Alice").single().execute()
print(result.data) # {"id": 1, "name": "Alice"}
Why?
- No Docker — no
supabase start, no containers, no waiting - No network — everything runs in-process with SQLite
- No config — one line to get a working client
- Same API — mirrors
supabase-py's query builder interface - Auto-creates tables — just insert data, tables and columns appear automatically
Install
pip install supabase-py-lite
Usage
from supabase_py_lite import create_client
# In-memory (for testing)
supabase_client = create_client(":memory:")
# File-based (for persistence)
supabase_client = create_client("./my_app.db")
Insert
supabase_client.table("users").insert({"id": 1, "name": "Alice", "age": 30}).execute()
# Batch insert
supabase_client.table("users").insert([
{"id": 2, "name": "Bob", "age": 25},
{"id": 3, "name": "Charlie", "age": 35},
]).execute()
Select
# All rows
res = supabase_client.table("users").select("*").execute()
# Specific columns
res = supabase_client.table("users").select("id, name").execute()
# With count
res = supabase_client.table("users").select("*", count="exact").execute()
print(res.count) # 3
# Single row
res = supabase_client.table("users").select("*").eq("id", 1).single().execute()
print(res.data) # {"id": 1, "name": "Alice", "age": 30}
# Maybe single (returns None instead of raising)
res = supabase_client.table("users").select("*").eq("id", 999).maybe_single().execute()
print(res.data) # None
Filters
.eq("col", value) # col = value
.neq("col", value) # col != value
.gt("col", value) # col > value
.gte("col", value) # col >= value
.lt("col", value) # col < value
.lte("col", value) # col <= value
.in_("col", [a, b]) # col IN (a, b)
.like("col", "A%") # col LIKE 'A%'
.ilike("col", "a%") # case-insensitive LIKE
.is_("col", None) # col IS NULL
.contains("col", val) # JSON array contains val
# Chain them
res = (
supabase_client.table("users")
.select("*")
.gte("age", 25)
.lte("age", 35)
.order("age", ascending=False)
.limit(10)
.execute()
)
Update
supabase_client.table("users").update({"name": "Alicia"}).eq("id", 1).execute()
Delete
supabase_client.table("users").delete().eq("id", 1).execute()
Upsert
supabase_client.table("users").upsert({"id": 1, "name": "Alice v2"}).execute()
# Custom conflict column
supabase_client.table("users").upsert(
{"email": "a@b.com", "name": "A"}, on_conflict="email"
).execute()
JSON columns
supabase_client.table("posts").insert({
"id": 1,
"meta": {"tags": ["python", "supabase"], "draft": False}
}).execute()
res = supabase_client.table("posts").select("*").eq("id", 1).execute()
print(res.data[0]["meta"]["tags"]) # ["python", "supabase"]
Foreign Keys
Register FK relationships with define_foreign_key, then use embedded resource syntax in .select() to join related data — exactly like Supabase.
# posts.user_id -> users.id
supabase_client.define_foreign_key("posts", "user_id", "users")
# comments.post_id -> posts.id
supabase_client.define_foreign_key("comments", "post_id", "posts")
Many-to-one (FK on current table → embeds as a single object):
res = supabase_client.table("posts").select("id, title, users(name, email)").execute()
# [{"id": 1, "title": "Hello", "users": {"name": "Alice", "email": "alice@example.com"}}, ...]
One-to-many (FK on related table → embeds as a list):
res = supabase_client.table("posts").select("id, title, comments(body)").execute()
# [{"id": 1, "title": "Hello", "comments": [{"body": "Great post!"}, ...]}, ...]
Wildcard on main table:
res = supabase_client.table("posts").select("*, users(name)").execute()
Alias — rename the embedded key in the result:
res = supabase_client.table("posts").select("id, author:users(name)").execute()
# [{"id": 1, "author": {"name": "Alice"}}, ...]
Hint — explicitly specify which FK column to use (useful when multiple FKs point to the same table):
res = supabase_client.table("posts").select("id, users!user_id(name)").execute()
Multiple embeds in one query:
res = supabase_client.table("posts").select("id, users(name), comments(body)").execute()
The FK join column is added to the query automatically if needed and stripped from the result unless you selected it explicitly.
Use in tests
import pytest
from supabase_py_lite import create_client
@pytest.fixture
def db():
supabase_client = create_client(":memory:")
yield supabase_client
supabase_client.close()
def test_create_user(db):
db.table("users").insert({"id": 1, "name": "Alice"}).execute()
res = db.table("users").select("*").eq("id", 1).single().execute()
assert res.data["name"] == "Alice"
Supported API
| Method | Status |
|---|---|
.from_() / .table() |
✅ |
.select() |
✅ |
.insert() |
✅ |
.update() |
✅ |
.delete() |
✅ |
.upsert() |
✅ |
.eq/.neq/.gt/.gte/.lt/.lte |
✅ |
.in_/.like/.ilike/.is_ |
✅ |
.contains |
✅ |
.order/.limit/.offset/.range |
✅ |
.single/.maybe_single |
✅ |
count="exact" |
✅ |
| JSON columns | ✅ |
| Auto-create tables | ✅ |
| Foreign key joins | ✅ |
.or_() |
✅ |
.not_() |
✅ |
Development
git clone https://github.com/acelikyurek/supabase-py-lite
cd supabase-py-lite
uv sync --group dev
uv run pytest -v
License
MIT
Metadata
Release files for supabase-py-lite 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| supabase_py_lite-1.0.0.tar.gz | 73.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| supabase_py_lite-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 95.8 kB
Release files / supabase_py_lite-1.0.0.tar.gz
| Download URL | supabase_py_lite-1.0.0.tar.gz |
|---|---|
| Size | 73.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
983f67277b8cb09de4391571cb45bfe530e25ea927cc594db68802f66f7066a4
|
|
BLAKE2b-256 checksum How to use checksums |
f33dc6309052859c7706147995f04dd01baf4f647494f516bf3ada4507b6e281
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / supabase_py_lite-1.0.0-py3-none-any.whl
| Download URL | supabase_py_lite-1.0.0-py3-none-any.whl |
|---|---|
| Size | 22.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e2335b73e09337def7769c342bac453ec7967cd31f20b0d23c4b55297cf6b680
|
|
BLAKE2b-256 checksum How to use checksums |
af5bb7889dbb021f95a8c40faa445bfe73e4aa477e637bd69f805d5bb5ca387e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|