文档表格和数据
行动
@Action 将 Python 函数转换为 POST 端点。与标准 API 处理程序不同,它被设计为类型化通信桥:Nexy 自动生成 TypeScript 客户端、验证输入并处理响应。
开始之前: 由快速启动创建的项目。操作位于页面旁边的 src/actions/ 中。
Client (UI) ─── 1. Call (Typed Input) ───► [ Nexy Bridge ]
│
▼
2. Validate & Execute (Python)
│
▼
Client (UI) ◄─── 5. Consume (Typed Result) ◄── 4. Serialize ◄── 3. Result行动周期
1. 定义
动作是一个标有 @Action 的普通 Python 函数。 Nexy 使用其签名来定义 API 合约。
from pydantic import BaseModelfrom nexy.decorators import Action, revalidate class User(BaseModel): name: str age: int = 0 @Actionasync def create_user(name: str, age: int = 0) -> User: revalidate() return User(name=name, age=age)参数是输入,由 Pydantic验证。名为 request 或 response 的参数被注入,但不是输入的一部分。
2. 验证
当调用该操作时,Nexy 会根据函数的类型提示验证输入。如果字段丢失或类型错误,则不会执行操作,并返回 422 Unprocessable Entity。
3. 执行
逻辑在服务器上运行。由于它是标准的 Python 函数,因此您可以使用任何库、访问数据库或调用其他服务。您还可以使用 revalidate() 触发页面刷新。
4. 序列化
返回值自动序列化为JSON。 Pydantic 模型是本机处理的,标准 Python 类型映射到它们的 JSON 等效项。
5. 消费
前端接收具有完全类型安全的结果。 Nexy 根据您的签名写入 __nexy__/src/actions.ts,然后 @nexy/actions 解析为它。
import { useAction } from "@nexy/client";import { create_user } from "@nexy/actions"; export default function NewUser() { const { run, pending, error, data } = useAction(create_user); return ( <button disabled={pending} onClick={() => run({ name: "Ada" })}> {error ? error.message : data ? `Created ${data.name}` : "Create"} </button> );}该组件是控制器返回的视图的一个岛:
import { useAction } from "@nexy/client";import { create_user } from "@nexy/actions"; export function NewUser() { const { run, pending, error, data } = useAction(create_user); return ( <button disabled={pending} onClick={() => run({ name: "Ada" })}> {error ? error.message : data ? `Created ${data.name}` : "Create"} </button> );}from "@/components/NewUser.tsx" import NewUser<NewUser directive="client:load" />如果没有钩子,动作只是一个 async 函数:const user = await create_user({ name: "Ada" })。
| 框架 | useAction 返回什么 |
|---|---|
| React,Preact | { pending, data, error, run, reset } |
| Solid | 相同,使用 pending()、data() 和 error() 作为访问器 |
| Vue | 相同,以 pending、data 和 error 作为参考 |
| Svelte | 商店:$save.pending、$save.data、$save.error,加上 save.run(input) 和 save.reset() |
useAction(action, { onSuccess, onError }) 也需要两个回调。 run 永远不会抛出:失败落在 error 中,并且 run 通过 undefined 解决。
类型来自 Python:str 是 string,int 和 float 是 number,list[T] 是 T[],Pydantic 模型或 Enum 是命名类型,并且具有默认值的字段在输入中是可选的。无法描述的类型 Pydantic 变为 unknown。
错误和失败
一项操作可能会在不同阶段失败。您应该在 UI 中区分它们:
- 验证错误 (422):输入与 Python 签名不匹配。
error.fields将每个无效字段映射到其消息。 - 业务错误 (
HTTPException):您的代码拒绝调用,例如403 Forbidden或409 Conflict。 - 服务器错误 (500):您的 Python 代码中发生未处理的异常。
- 网络错误:请求从未到达服务器,例如超时或连接丢失。
- 授权错误 (401, 403):您自己的检查拒绝了调用者。 Nexy 不会为您做出决定。
下面描述的来源检查是 CSRF 保护,而不是授权:它说明呼叫来自哪里,而不是允许谁拨打。
执行操作后刷新页面
一个操作表明哪些页面的更改变得过时,就像 Next.js 对 revalidatePath 所做的那样:
revalidate() # the page that is openrevalidate("/users") # that page, if it is the one open
答案在 x-nexy-revalidate 标头中携带路径。然后客户端通过路由器重新获取当前页面,或者在没有客户端路由器时重新加载当前页面。
没有 JavaScript 的表格
操作也采用标准 HTML 表单,因此页面在加载任何脚本之前即可运行:
<form method="post" action="{{ __actions_path['create_user'] }}"> <input name="name"> <button type="submit">Create</button></form>
在 .nexy 模板中,__actions_path["name"] 是路径,__actions["name"] 是函数,并且该函数也在其自己的名称下的范围内。
成功后,访问者将被发送回表单所在的页面,状态为 303。要以不同方式回答,请返回 Response 对象(例如 RedirectResponse("/done", status_code=303))。
作为 application/x-www-form-urlencoded 发送的表单(这是浏览器默认执行的操作)不需要额外的包。添加 enctype="multipart/form-data" 并且调用需要 python-multipart:如果没有它,操作会回答 500,并带有一条命名命令 uv add python-multipart 的消息。
操作读取字段,而不是文件:文件输入作为字符串到达它。要接收上传,请在 src/routes/api/ 中使用 File 或 UploadFile 参数编写路由。
安全
路径模糊不等于授权
操作路径是 /_nx_,后跟函数模块的哈希值、其名称和项目的秘密。它由 NEXY_ACTION_SECRET 设置,或保存在 __nexy__/action.key 中,由 nx build 创建,因此客户端捆绑包和 nx start 同意:将该文件与构建一起保留。该路径隐藏端点以防止偶然发现,并且 生成的路径不是授权机制。
任何知道路径的人都可以调用该操作。您 必须 在操作中验证用户的身份和权限:
@Actionasync def delete_user(user_id: str, request: Request): if not request.state.user.is_admin: raise HTTPException(403, "Admin rights required") # ...
原产地保护
为了防止跨站点请求伪造 (CSRF),Nexy 拒绝 Origin 是另一个站点或浏览器用 403 标记 Sec-Fetch-Site: cross-site 的请求。在 NEXY_ALLOWED_ORIGINS 中列出可能调用您的操作的站点。接受没有 Origin(例如 curl)的呼叫。
导入错误
启动时,Nexy 导入 src/ 的每个模块以查找操作。报某个模块导入失败,其他的依然加载:
x src.actions.billing: src/actions/billing.py:3 ModuleNotFoundError: No module named 'stripe'