# 轻量 Python 项目后端规范

每个项目对应一个独立 Python 进程和一个私有 SQLite 数据库。项目代码由受限 AST 解释器处理，不传给 Python 的 `eval` 或 `exec`，不依赖 Docker。REST 和 WebSocket 的连接由 FastAPI 管理，业务函数在项目进程里执行。

## 快速开始

在项目的“Python 后端”控制台中编辑代码，点击“安全检查并保存”，再点击“启动”。停止会关闭进程，但保留代码和数据库。重启加载已保存的代码。服务正常重启后，会重新启动之前处于启动状态的项目。

```python
from backend_sdk import app, db, response

@app.get("/hello")
def hello(request):
    name = request["query"].get("name", "世界")
    return {"message": f"你好，{name}"}

@app.post("/notes")
def add_note(request):
    text = str((request["json"] or {}).get("text", "")).strip()
    if not text:
        return response({"error": "text 不能为空"}, status=400)
    db.execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL)")
    result = db.execute("INSERT INTO notes(text) VALUES (?)", [text])
    print("note created", result["lastrowid"])
    return response({"id": result["lastrowid"]}, status=201)

@app.get("/notes")
def list_notes(request):
    db.execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL)")
    return {"notes": db.query("SELECT id, text FROM notes ORDER BY id DESC LIMIT 100")}

@app.websocket("echo")
def echo(event):
    return {"echo": event["data"]}
```

仓库里的 `examples/backend.py` 是可直接使用的完整示例。每个处理函数只接收一个参数；帮助函数可以使用普通参数和默认值。

## REST 请求与响应

网站 `/xxx/my_project/` 的 REST 基地址是 `/xxx/my_project/api/`。上述 hello 路由对应 `GET /xxx/my_project/api/hello?name=Alice`。

支持 `@app.get`、`@app.post`、`@app.put`、`@app.patch`、`@app.delete`。路由必须以 `/` 开头；支持简单路径参数，例如 `/notes/{id}`，用 `request["params"]["id"]` 读取。按代码定义顺序匹配路由，应把具体路由放在参数路由前面。

`request` 是普通字典：

| 键 | 含义 |
| --- | --- |
| `method` | 请求方法，英文大写 |
| `path` | 项目后端相对路径，如 `/hello` |
| `query` | 查询参数字典，重复键取最后一个值 |
| `json` | JSON 请求体，无请求体时为 None |
| `headers` | 小写请求头字典；管理端 Cookie 和 CSRF 头不会传入业务代码 |
| `params` | 路由路径参数字典，值都是字符串 |

直接返回字典、列表、字符串、数字、布尔值或 None，服务会将其转成 JSON，默认状态 200。需要指定状态码时，用 `response(body, status=201)`，允许 200–599。响应最大 512 KiB，REST JSON 请求体最大 1 MiB。当前不支持二进制响应、流式响应、任意响应头或其他服务器框架。

公开后端接口不会自动继承管理账号身份。需要业务登录时，由项目代码自行设计身份验证，例如验证请求的 Authorization 头。公开 API 允许跨源请求，适配静态网页的隔离来源；不要在代码中依赖管理会话 Cookie。

首页 HTML 中可以使用相对路径：

```javascript
const data = await fetch('./api/hello?name=Alice').then(r => r.json());
await fetch('./api/notes', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({text: '我的第一条记录'})
});
```

子目录 HTML 应按所在层级使用 `../api/hello` 等相对路径。`api/` 和 `ws` 是项目后端保留访问路径，请不要把静态资源放在这些路径。

## WebSocket 事件

连接 `ws://服务器/xxx/my_project/ws`，HTTPS 时使用 `wss://`。发送 JSON 消息：

```json
{"event":"echo","data":{"message":"hello"}}
```

`@app.websocket("echo")` 注册事件。处理函数参数是 `{event, data, connection_id}`；返回值会发送给当前连接：

```json
{"event":"echo","data":{"echo":{"message":"hello"}},"status":200}
```

事件名使用字母、数字、下划线、连字符，最大 100 字符。未知事件返回状态 404，超限返回 429，未启动返回 503。单条客户端消息最大 64 KiB。当前模型是事件/响应，不提供主动广播、后台任务、异步 Python 协程或连接对象。每个进程按顺序执行请求，多个浏览器连接共享项目数据库和配额。

## SQLite SDK

代码使用 `db`，不直接打开数据库文件：

- `db.execute(sql, params=[])`：支持 CREATE TABLE/INDEX、INSERT、UPDATE、DELETE、DROP TABLE/INDEX、ALTER TABLE，返回 `{lastrowid, rowcount}`。
- `db.query(sql, params=[])`：支持 SELECT/WITH，返回字典列表。最多返回 1000 行，请使用 LIMIT 分页；BLOB 值以十六进制字符串返回。
- 使用 `?` 和参数数组绑定变量，避免拼接用户输入生成 SQL。
- 一个处理函数中的数据库操作属于同一个事务；处理成功且响应有效时提交，异常时回滚。
- 数据库位于 `DATA_DIR/backends/项目数字ID/database.sqlite`，不会作为静态文件公开。停止进程后可以备份该文件。
- 默认持久数据库文件上限 10 MiB，通过 SQLite 页数限制执行；写满返回 507。监控显示数据库及存在的临时 journal 文件的实际大小。事务期间 SQLite 回滚日志可能临时占用额外空间，事务结束会释放。
- 禁止 ATTACH/DETACH、PRAGMA、扩展加载、文件读写型 SQL、手动连接和关闭。SDK 在每次请求中重新施加数据库限制。

## 支持的 Python 语法

支持普通函数、模块级常量、赋值、字典/列表/元组字面量、索引/切片、if/elif/else、for、while、break/continue、基础算术/比较/布尔运算、f-string、列表/字典推导式、try/except/finally、raise ValueError/Exception。处理函数必须只有一个普通参数，不使用 `*args`/`**kwargs`。导入与函数定义应放在模块顶层。

可使用的基础函数：`len`、`str`、`int`、`float`、`bool`、`list`、`dict`、`min`、`max`、`sum`、`abs`、`round`、`sorted`、`enumerate`、`range`、`print`。`range` 最多 100000 个元素。字典支持 get/items/keys/values/copy/update/pop；列表支持 append/extend/pop/count/index/reverse；字符串支持 lower/upper/strip/lstrip/rstrip/split/join/replace/startswith/endswith/isdigit/count。

允许 `import json`（loads/dumps）、`import math`（ceil/floor/sqrt/sin/cos/log/log10/isfinite/pi/e），以及从 `backend_sdk` 导入 app、db、response。这些模块是解释器开放的能力表，不暴露原生 Python 模块内部结构。仅支持此规范列出的语法和成员；这不是运行任意第三方 Python 包的环境。

## 安全检查与资源限制

保存前和启动前都检查 AST：禁止任意导入、私有属性/双下划线名称、eval/exec/compile/open、反射、类定义、lambda、异步/生成器、全局/非局部声明等能力。模块顶层不能执行请求或数据库写入。

运行时只解释白名单 AST，不编译执行项目代码；属性访问只支持显式开放的 SDK 和基础类型方法。每个请求最多 100000 个执行步，函数递归最多 32 层，并有时间和响应大小限制。SQLite 长查询也有执行时间检查。解释器限制和进程隔离是多层防护，不能替代服务器操作系统补丁和运维隔离；进程采用服务器账号运行，但项目语言没有文件、网络、进程或系统调用能力。

默认项目配额：SQLite 10 MiB、REST + WebSocket 事件合计每秒 100 次、独立进程 128 MiB、处理时间 2 秒。管理员可在监控页面修改数据库/QPS/内存/超时。Windows 使用 Job Object 限制进程内存和子进程数量，POSIX 使用 RLIMIT_AS；内存限制设置失败会拒绝启动。排队和执行超时都有上限；进程执行超时或退出后需要从控制台重新启动。

## 管理 API 与 Agent

认证使用管理端的 Cookie + CSRF，与文件工作区 API 相同：

| 方法 | 路径 | 作用 |
| --- | --- | --- |
| GET | `/api/sites/{id}/backend` | 私有代码及运行状态 |
| GET | `/api/sites/{id}/backend/status` | PID、CPU、内存、数据库、配额、请求数及日志 |
| POST | `/api/sites/{id}/backend/check` | JSON `{source}`，只做检查 |
| PUT | `/api/sites/{id}/backend` | JSON `{source}`，检查并保存，停止旧进程 |
| POST | `/api/sites/{id}/backend/start` | 启动 |
| POST | `/api/sites/{id}/backend/stop` | 停止，保留数据库 |
| POST | `/api/sites/{id}/backend/restart` | 重启 |
| GET | `/api/admin/metrics` | 管理员读取全站监控快照 |
| WebSocket | `/api/admin/live` | 管理员认证后每秒接收监控快照 |
| PATCH | `/api/admin/projects/{id}/limits` | 管理员修改 database_limit_mb/qps_limit/memory_limit_mb/timeout_seconds；运行中的项目自动重启应用配置 |

普通用户只管理自己的后端，管理员可通过监控控制项目启停和配额。首次注册账号成为管理员；升级已有数据时，第一个已有账号取得管理员身份。其他账号不会自动成为管理员。

管理页内嵌 OpenAPI 3.1 和 Agent 指南。支持 WebMCP 的浏览器会发现后端检查/保存/启停/状态等工具；没有 WebMCP 时可以直接使用本节 API 和 `/api/openapi.json`。仅在用户授权的开发任务内操作代码和启停进程。

## 数据库在线管理

“数据库管理”是独立管理页面，可选择项目、浏览表结构和分页数据、用 JSON 编辑记录、新增或确认删除记录，并提供 SQL 控制台。SQL 使用同一套权限/配额约束，在独立的限时进程中执行。普通用户只管理自己的数据，管理员具有项目管理权限。

支持导出一致的 SQLite 快照，即使后端正在运行。导入需要明确确认，会替换整个数据库；先验证文件格式、完整性、大小以及普通表/索引结构，再暂停项目进程并切换数据库，最后恢复原来的运行状态。验证失败保留原数据。当前导入不接受视图、触发器、虚拟表。

数据库接口：GET `/api/sites/{id}/database`；GET/POST/PATCH/DELETE `/api/sites/{id}/database/tables/{table}`；POST `/api/sites/{id}/database/query`（`{sql,params}`）；GET `/api/sites/{id}/database/export`；POST `/api/sites/{id}/database/import`（multipart `file` + `confirm=true`）。行写入使用 `{values,key}`，key 应使用行查询接口返回的标识；删除另需 `confirm=true`。字段类型支持 JSON 基础类型，BLOB 用 `{"$blob":"base64内容"}`。

## 本地检查与运行

控制台提供 [本地解释器下载](/downloads/local-runner.zip) 和 [本地测试说明](/local-runner-guide.md)。解压后安装 requirements，再运行 `python local_runner.py check examples/backend.py` 或 `python local_runner.py serve examples/backend.py --database local.sqlite`。本地仅监听 127.0.0.1:8787，使用单独的本地数据库，不请求云端、不改变云端状态；与服务端使用同一语言和 SDK。
