supie
Python edge functions for Supabase.
supie builds a plain Python handler into an ordinary Supabase Edge Function. The function runs Python on WebAssembly inside Supabase's Edge Runtime, so you deploy it with the Supabase CLI like any TypeScript function. supie also adds workflows with parallel steps and retries, queues with exactly-once results and ONNX models that score at native speed.
from supie import App, HTTPError, Request
app = App()
@app.post("/")
def score(req: Request):
applicant = req.json()
if "age" not in applicant:
raise HTTPError(422, "missing: age")
return {"score": 600 + points(applicant["age"])}
supie is an independent almostly project, unaffiliated with Supabase.
Install
pip install supie # or, in a uv project: uv add --dev supie
Install it in your project's environment: your tests import supie.testing, and the build copies supie's runtime into each function, so deployed functions don't need it installed.
You also need the Supabase CLI. The Pyodide runtime needs Deno or Node.js at build time, to build its snapshot.
Quickstart
In a Supabase project (run supabase init first if you don't have one):
supie init hello # py/hello/main.py and py/hello/test_main.py
pytest py/hello # handlers are plain Python
supie build # writes supabase/functions/hello/
supie serve # supabase functions serve, rebuilding when py/ changes
supie deploy # supabase functions deploy, after building
Your Python lives in py/<name>/, and supie build turns it into supabase/functions/<name>/. Treat the generated folder like a dist/ directory: edit py/, never the output.
Performance
supie runs your code on MicroPython by default, or on Pyodide (CPython compiled to WebAssembly) for the full standard library and packages. On hosted Supabase, where nearly every request gets a fresh worker:
| bundle | cold boot | cold request, end to end | |
|---|---|---|---|
| MicroPython | 0.2 MB | 19 ms | 0.18 s |
| Pyodide, from a snapshot | 12 MB | 300 ms | 0.74 s |
Handling a request costs about the same as TypeScript, but a Python loop runs 45 to 170 times slower, and hosted functions get 2 s of CPU per request. supie fits request logic, validation, scoring and glue code.
In a one-hour run on hosted Supabase, 99.95% of 3,916 scoring requests and 99.77% of 4,258 queued applications succeeded, with no server errors, and every application ended exactly once. See performance and reliability.
Handlers
Routes take typed path parameters, handlers can be sync or async, and req.supabase is an async Supabase client that acts as the caller, so row level security applies:
@app.get("/loans/{loan_id:int}")
async def get_loan(req: Request, loan_id: int):
rows = await req.supabase.table("loans").select("id, amount").eq("id", loan_id).execute()
if not rows.data:
raise HTTPError(404, "loan not found")
return rows.data[0]
See handlers and the Supabase client.
Workflows
A Flow chains steps, like AWS Step Functions over your edge functions. Independent steps run in parallel, failed steps retry, and every finished step is saved, so a run can resume where it stopped. Runs can also go to pgflow, so Postgres schedules each step and runs outlive any worker.
scoring = Flow("scoring")
@scoring.step(retries=2, timeout=5)
async def bureau(ctx, application):
return await ctx.invoke("bureau-fetch", application)
@scoring.step(depends_on=[bureau])
def decide(ctx, application, bureau):
return {"decision": "approve" if bureau["score"] > 650 else "decline"}
See workflows and the flow walkthrough notebook.
Queues
A queue on pgmq, the engine behind Supabase Queues, accepts work fast and handles it in the background, with retries and a dead-letter table. Send keys and ctx.once make each result happen exactly once, even when a client retries or a message is delivered twice:
@app.post("/score")
async def score(req: Request) -> Response:
application = req.json()
await queue.send(application, key=f"application-{application['id']}")
return Response({"queued": True}, status=202)
@queue.consumer(max_attempts=5)
async def handle(ctx, message):
await ctx.once(f"decide:{message['id']}", lambda: decide(message))
See queues.
Models
Supabase's Edge Runtime has a native ONNX engine, and supie gives Python handlers access to it, so a model scores at native speed. supie model publish takes a model from an MLflow registry alias and uploads it to a private bucket:
@app.on_boot
async def setup():
app.state.model = await Model.from_storage("models", "pd/v1.onnx")
See models.
Testing
Handlers are plain Python, so you test them with pytest and no Supabase:
def test_score():
client = TestClient(app, env={"SCORING_KEY": "test"})
assert client.post("/score", json={"age": 40}).status == 201
See testing.
Documentation
- Runtimes and packages: choosing between MicroPython and Pyodide, payload sizes, PyPI packages and preloading them into the snapshot
- How it works: the build, the TypeScript shim and the limitations
Development
uv sync
uv run pytest -m "not edge and not postgres" # unit tests
uv run pytest -m "edge or postgres" # Edge Runtime, Postgres, PostgREST and pgflow, needs Docker
uv run ty check src tests scripts examples # types
CI runs the Docker tests against pinned images, and a weekly workflow runs them against the images the Supabase CLI ships that day.
Metadata
Release files for supie 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| supie-0.1.2.tar.gz | 204.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| supie-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 280.4 kB
Release files / supie-0.1.2.tar.gz
| Download URL | supie-0.1.2.tar.gz |
|---|---|
| Size | 204.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9250800c947e63ec4f37cff6311dee2a271fff17998d598976334c4e2aff7461
|
|
BLAKE2b-256 checksum How to use checksums |
4ec1591b053482462214e180cc9c298e2c78078adb41e5c6883f219970eeab07
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / supie-0.1.2-py3-none-any.whl
| Download URL | supie-0.1.2-py3-none-any.whl |
|---|---|
| Size | 75.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5b2afa5032a8fc805df6b9dad0bd2234f9e0b64a4f91f39c73dc4c0d4ef8c5ef
|
|
BLAKE2b-256 checksum How to use checksums |
0486cf31b8ee494c860c95d28c91b6bf16360feddf0fb4699203e73d77bbe3e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for supie 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.