Python

With `"python"` in `back-end`, the generator writes a FastAPI application with one router per role and model, on top of a small `app/core` runtime for the database, CRUD, row ownership and auth.

The Python backend targets PostgreSQL: set "postgresql": true so the generated SQL matches. The rural farming platform (apac-genaiacademy-c2) is a production app built this way, with Python, SolidJS and PostgreSQL.

Configure and run

{ "back-end": ["python"], "postgresql": true }
php setup.php
psql -d app -f database/Migration.sql
cd python
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env            # POSTGRES_* and JWT_SECRET
uvicorn app.main:app --reload --port 8000

Generated structure

python/app/
├── main.py                         FastAPI app: auth router + all_routers (+ custom-role guards)
├── core/                           runtime (scaffold, see below)
├── api/
│   ├── auth.py                     POST /login, POST /register, GET /login
│   ├── routers.py                  all_routers = [...]   (regenerated)
│   └── <scope>/<model>/<model>.py  APIRouter(prefix="/<scope>/<model>")
├── models/<model>.py               Pydantic <Model> and <Model>Input
├── orm/<model>.py                  table, fillable, relations (extends app.core.model.Model)
└── services/<model>_service.py     CrudService subclass + get_service()
  • New projects, where python/app/main.py doesn’t exist yet, get the whole scaffold.
  • Existing projects only get the app/core modules they’re missing. Your main.py, auth and any customised core files are never touched.

Routes

The scope is the role namespace from the schema’s crud: isuper, islogin, ipublic (or public), or a custom role.

ScopeWho can call it
isuperAdmins. get_current_admin is attached to the router, and queries are unscoped.
isloginAny signed-in user. Queries are limited to rows that user owns.
custom role, e.g. executive (prefix /executive/<model>)Users holding that role, or isuper. The template main.py adds require_role(scope, "isuper"). Queries are owner-scoped.
ipublic / publicEveryone.
LetterRoute
aGET /
wPOST /where
rGET /{item_id}
cPOST / (returns 201)
uPUT /{item_id}
pPOST /upsert
dDELETE /{item_id} (returns {"success": true})

The app/core runtime

ModuleRole
db.pyThe DB query builder over psycopg 3. It uses SQLAlchemy only as a connection pool. Settings come from POSTGRES_SERVER, POSTGRES_PORT, POSTGRES_DB, POSTGRES_USER and POSTGRES_PASSWORD; the pool size from DB_POOL_SIZE and DB_MAX_OVERFLOW.
model.pyA fluent Model in the style of the PHP runtime: all, where, find, create, update, upsert, delete, paginate and with_rel.
crud_service.pyCrudService, which every generated service extends. It does the CRUD with parameterized SQL and applies the ownership rules whenever it’s called with owner=.
ownership.pyThe row-ownership rules for /islogin/*.
auth.pyBearer JWT auth. It provides get_current_active_user and get_current_admin.

Row ownership

/islogin/* routes pass the signed-in user as owner. /isuper/* and /ipublic/* routes don’t.

  • Default: a table with a user_id column is scoped to t.user_id = <me>, and on create user_id is forced to the signed-in user, whatever the client sent.
  • Explicit rules go in ownership.py:
_FARMS = "SELECT id FROM farms WHERE user_id = {uid}"
OWNERSHIP = {"farms": "t.user_id = {uid}", "farm_plots": f"t.farm_id IN ({_FARMS})"}
PARENTS = {"farm_plots": {"farm_id": "farms"}}          # can't attach a plot to someone else's farm
OWNER_COLUMNS = {"farms": ["user_id", "owner_id"]}     # forced to the signed-in user on create
SHARED_READ = {"veterinarians"}                        # readable by everyone, writes still scoped

Auth

  • Tokens are HS256 JWTs signed with JWT_SECRET. They expire after JWT_TTL_SECONDS (one day by default).
  • Users, roles and active_roles are the same framework tables the PHP and Deno runtimes use.
  • Passwords are sha3-256 hex, the same as the PHP runtime, so one users table can serve both.
  • A user is isuper if they are user 1, or if they have an active_roles row pointing at the role named isuper.
RouteResult
POST /register with {name, email, password}{token, id, name, email, roles}. 422 if the email is taken.
POST /login with {email, password}{token, …}. 404 for an unknown user, 401 for a wrong password, 403 if disabled.
GET /login with Authorization: Bearer <token>The current user.

To use a different identity provider, replace app/core/auth.py, but keep the names get_current_active_user and get_current_admin, because the generated routers import them. The farming platform does this with Firebase tokens.

Helper library: the_python

compile-php/libraries_dev/the_python is a separate FastAPI helper library. The generated code doesn’t import it. It provides:

  • ModelService
  • SqlBuilder
  • response helpers
  • Auth, with bcrypt-compatible hashing
  • Session
  • FileAct, which saves uploads under storage/
  • Mail, which reads its SMTP settings from environment variables