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.pydoesn’t exist yet, get the whole scaffold. - Existing projects only get the
app/coremodules they’re missing. Yourmain.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.
| Scope | Who can call it |
|---|---|
isuper | Admins. get_current_admin is attached to the router, and queries are unscoped. |
islogin | Any 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 / public | Everyone. |
main.py. If you replace main.py, keep that check. The farming platform does the same thing with its namespace_guards.| Letter | Route |
|---|---|
a | GET / |
w | POST /where |
r | GET /{item_id} |
c | POST / (returns 201) |
u | PUT /{item_id} |
p | POST /upsert |
d | DELETE /{item_id} (returns {"success": true}) |
The app/core runtime
| Module | Role |
|---|---|
db.py | The 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.py | A fluent Model in the style of the PHP runtime: all, where, find, create, update, upsert, delete, paginate and with_rel. |
crud_service.py | CrudService, which every generated service extends. It does the CRUD with parameterized SQL and applies the ownership rules whenever it’s called with owner=. |
ownership.py | The row-ownership rules for /islogin/*. |
auth.py | Bearer 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_idcolumn is scoped tot.user_id = <me>, and on createuser_idis 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
user_id column is not scoped: any signed-in user can read and change every row through /islogin/*. Either add a rule for it, or grant islogin only r and a in its schema.Auth
- Tokens are HS256 JWTs signed with
JWT_SECRET. They expire afterJWT_TTL_SECONDS(one day by default). - Users, roles and
active_rolesare the same framework tables the PHP and Deno runtimes use. - Passwords are sha3-256 hex, the same as the PHP runtime, so one
userstable can serve both. - A user is
isuperif they are user 1, or if they have anactive_rolesrow pointing at the role namedisuper.
| Route | Result |
|---|---|
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:
ModelServiceSqlBuilder- response helpers
Auth, with bcrypt-compatible hashingSessionFileAct, which saves uploads understorage/Mail, which reads its SMTP settings from environment variables