feat(examples): add FastAPI Realtime Rule Engine example#1519
Draft
grdsdev wants to merge 18 commits into
Draft
Conversation
…, and XSS in templates - Fix hardcoded watch_column check in engine.py to evaluate any column - Add WITH CHECK to tasks_update RLS policy to prevent reassignment - Reject empty Bearer token in dependencies.py auth guard - Replace innerHTML template literals with safe DOM construction in index.html and rules.html - Call sign_out() in POST /auth/signout for programmatic clients - Fix Python version in README (3.9+ -> 3.10+) - Add tests for non-status column rule firing and empty Bearer token
…stAPI example
Adds a full E2E test suite that exercises the FastAPI Realtime Rule Engine
example against a real local Supabase instance (via the Supabase CLI).
What's new:
- examples/fastapi/supabase/config.toml — local Supabase config (port 54341)
- examples/fastapi/Makefile — start-infra, stop-infra, tests, e2e targets
- examples/fastapi/tests/e2e/ — 20 async E2E tests:
- test_auth_e2e.py — signup/signin/signout + 401 guard
- test_tasks_e2e.py — CRUD + RLS (user B blocked from updating user A's task)
- test_rules_e2e.py — CRUD + RLS (user B cannot see or delete user A's rules)
- test_engine_e2e.py — Postgres Changes → rule_events insert within 15s poll
- .github/workflows/e2e-fastapi.yml — CI workflow (py3.10 + py3.12 matrix);
starts Supabase, applies migrations, runs the FastAPI app as a subprocess,
executes E2E tests
- Root Makefile — adds fastapi.% delegation target
The engine E2E test is the key SDK stress test: it verifies the full path
AsyncRealtimeClient → Postgres Changes callback → PostgREST INSERT + broadcast
by polling rule_events after a task status update.
…tderr, fail fast on crash
Three root causes fixed so all 20 E2E tests now pass:
1. Missing table-level GRANTs in migration
Supabase's default privileges are set for supabase_admin; migrations
run as postgres so authenticated and service_role had only REFERENCES/
TRIGGER/TRUNCATE — no SELECT/INSERT/UPDATE/DELETE. Added explicit
GRANT statements at the end of 001_initial.sql.
2. Wrong Realtime payload key in engine.py
supabase-py does NOT normalize Postgres Changes payloads to
payload["new"] like the JS SDK. The new record is at
payload["data"]["record"]. engine.py was calling payload.get("new",{})
which always returned {} and silently short-circuited the rule-match
loop, producing zero rule_events. Fixed to payload["data"]["record"].
3. E2E fixture teardown FK violations
GoTrue admin.delete_user() deletes from auth.users but cannot cascade
to application tables that REFERENCE auth.users without ON DELETE
CASCADE. Added _delete_user_and_data() helper that deletes rules and
tasks for the user before calling delete_user. Also fixed user_a/user_b
to use a dedicated anon client for sign_up instead of the service client
(sign_up was corrupting the service client's auth state).
Also added two new entries to friction_log.md for the payload key
mismatch and the service-role auth-state corruption.
- Drop `requires-python = ">=3.10"` → `">=3.9"` so the workspace syncs
under all CI Python versions
- Replace `str | None` / `dict | None` union syntax with `Optional[...]`
in test_dependencies.py and test_engine_e2e.py (PEP 604 is 3.10+)
- Fix test_engine.py unit tests to use the correct supabase-py Realtime
payload structure — `{"data": {"record": {...}}}` — matching the engine
fix in 0484de2 (was still using the old JS-style `{"new": {...}}` keys)
- Add `_TIMEOUT = 30` (up from 15) in test_engine_e2e.py for CI headroom
- Add `per-file-ignores = ["examples/fastapi/**" = ["B008"]]` in root
pyproject.toml to allow FastAPI's Depends()-in-default-args pattern
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a complete
examples/fastapi/example application that stress-tests the async Realtime path ofsupabase-py. The example is a FastAPI Realtime Rule Engine: users define watch rules ("whentasks.statuschanges todone, broadcast to channel#team-alerts"), and a Python background task holds a persistentAsyncRealtimeClientconnection, subscribes to Postgres Changes, evaluates matching rules, and broadcasts enriched events — all from Python.What's included
examples/fastapi/main.py— FastAPI app withlifespanmanaging the service role client and engine taskexamples/fastapi/engine.py—AsyncRealtimeClientbackground coroutine: Postgres Changes subscription + server-side broadcastexamples/fastapi/dependencies.py— Per-request JWT client injection viaAsyncClientOptionsheader mutationexamples/fastapi/routers/— Auth, tasks, and rules routers with RLS-enforced PostgREST queriesexamples/fastapi/templates/— Jinja2 HTML shell; browser uses@supabase/supabase-jsCDN for Realtime, presence, and authexamples/fastapi/supabase/migrations/001_initial.sql— Tables (tasks,rules,rule_events) with RLS policies;ALTER PUBLICATION supabase_realtime ADD TABLE tasksexamples/fastapi/friction_log.md— 6 documented SDK pain points surfaced during implementationexamples/fastapi/README.md— Setup and run instructionsSDK paths stressed
acreate_client(url, service_key)acreate_client(url, anon_key, options=...)channel.on_postgres_changes(event="UPDATE", ...)await service_client.realtime.connect()await channel.subscribe()await channel.send(type="broadcast", ...)await client.table(...).select("*").execute()Friction log highlights
The
friction_log.mddocuments 6 SDK issues found during implementation, including:AsyncClientOptions().headersafter construction (passingheaders={}to the constructor dropsDEFAULT_HEADERS)realtime.connect()must be called manually beforechannel.subscribe()— no auto-connectasyncio.get_running_loop().create_task())channel.send()requires a priorchannel.subscribe()on the broadcast channelTests
26 tests covering engine rule evaluation, JWT dependency injection, all router endpoints, SQL migration, and template rendering. No mocking of Supabase's network layer — mocks target only the SDK client objects.
Test plan
supabase db pushfromexamples/fastapi/.envwithSUPABASE_URL,SUPABASE_SERVICE_KEY,SUPABASE_ANON_KEYuv syncfrom repo root, thenuv run uvicorn main:app --reloadfromexamples/fastapi//signin, create tasks and rules, verify live broadcast events appear in the feed