3.8 KiB
| type | status | date | agent_author | tags | related_files | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| decision | active | 2026-03-20 | claude |
|
|
Context
When designing the Portfolio Manager data layer (Phase 1), the question arose:
should we use an ORM (specifically Prisma) or keep the raw supabase-py
client that the scaffolding already plans to use?
The options considered were:
| Option | Description |
|---|---|
Raw supabase-py (chosen) |
Direct Supabase PostgREST client, builder-pattern API |
Prisma Python (prisma-client-py) |
Code-generated type-safe ORM backed by Node.js |
| SQLAlchemy | Full ORM with Core + ORM layers, Alembic migrations |
The Decision
Use raw supabase-py without an ORM for the portfolio data layer.
The data access layer (supabase_client.py) wraps the Supabase client directly.
Our own Portfolio, Holding, Trade, and PortfolioSnapshot dataclasses
provide the type-safety layer; serialisation is handled by to_dict() /
from_dict() on each model.
Why Not Prisma
-
Node.js runtime dependency —
prisma-client-pyuses Prisma's Node.js engine at code-generation time. This adds a non-Python runtime requirement to a Python-only project. -
Conflicts with Supabase's migration tooling — the project already uses Supabase's SQL migration files (
migrations/001_initial_schema.sql) and the Supabase dashboard for schema changes. Prisma'sprisma migratemaintains its own shadow database and migration state, creating two competing systems. -
Code generation build step — every schema change requires running
prisma generatebefore the Python code works. This complicates CI, local setup, and agent-driven development. -
Overkill for 4 tables — the portfolio schema has exactly 4 tables with straightforward CRUD. Prisma's relationship traversal and complex query features offer no benefit here.
Why Not SQLAlchemy
-
Not using a local database — the database is managed by Supabase (hosted PostgreSQL). SQLAlchemy's connection-pooling and engine management are designed for direct database connections, which bypass Supabase's PostgREST API and Row Level Security.
-
Extra dependency — SQLAlchemy + Alembic would be significant new dependencies for a non-DB-heavy app.
Why Raw supabase-py Is Sufficient
supabase-pyprovides a clean builder-pattern API:client.table("holdings").select("*").eq("portfolio_id", id).execute()- Our dataclasses already provide compile-time type safety and lossless serialisation; the client only handles transport.
- Migrations are plain SQL files — readable, versionable, Supabase-native.
SupabaseClientis a thin singleton wrapper that translates HTTP errors into domain exceptions — this gives us the ORM-like error-handling benefit without the complexity.
Constraints
- Do not add an ORM dependency (
prisma-client-py,sqlalchemy,tortoise-orm) topyproject.tomlwithout revisiting this decision. - Do not bypass
SupabaseClientby importingsupabasedirectly in other modules — always go throughPortfolioRepository. - If the schema grows beyond ~10 tables or requires complex multi-table joins,
revisit this decision and consider SQLAlchemy Core (not the ORM layer) with
direct
asyncpgconnections.
Actionable Rules
- All DB access goes through
PortfolioRepository→SupabaseClient. - Migrations are
.sqlfiles intradingagents/portfolio/migrations/, run via the Supabase SQL Editor orsupabase db push. - Type safety comes from dataclass
to_dict()/from_dict()— not from a code-generated ORM schema.