In-memory Supabase client for testing. Drop-in replacement for supabase-py backed by SQLite.
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file supabase_py_lite-1.0.0.tar.gz.
File metadata
- Download URL: supabase_py_lite-1.0.0.tar.gz
- Upload date:
- Size: 73.5 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
983f67277b8cb09de4391571cb45bfe530e25ea927cc594db68802f66f7066a4
|
|
| MD5 |
eb8bc76c1a11109d2eded457ed3b3637
|
|
| BLAKE2b-256 |
f33dc6309052859c7706147995f04dd01baf4f647494f516bf3ada4507b6e281
|
File details
Details for the file supabase_py_lite-1.0.0-py3-none-any.whl.
File metadata
- Download URL: supabase_py_lite-1.0.0-py3-none-any.whl
- Upload date:
- Size: 22.2 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2335b73e09337def7769c342bac453ec7967cd31f20b0d23c4b55297cf6b680
|
|
| MD5 |
bf94bd55dafe7540fdd44f3faaff0396
|
|
| BLAKE2b-256 |
af5bb7889dbb021f95a8c40faa445bfe73e4aa477e637bd69f805d5bb5ca387e
|