Lập trình viên / Tra cứu
Mini Server: API và SDK Python
Handler, danh tính request.user, Admin Database theo Project, truy vấn và kết quả chưa xác định.
Khai báo handler
Mỗi async handler nhận một Request. Dùng api.get/post/put/patch/delete/head/options hoặc api.route(method, path). Đường dẫn trong code không chứa tiền tố /api. Tối đa 64 route; HEAD cần khai báo riêng.
Request có method, path, params, headers, body (bytes), query và user. query.getlist(name) lấy tham số lặp; await request.json() đọc JSON. user là None hoặc snapshot Project Auth đã xác thực gồm id, project_id, roles, groups, is_admin, authz_version. roles/groups là tuple chỉ đọc. Cookie và token không đưa vào handler; không tin user/role do client gửi trong body hoặc header riêng.
Trả JSON, chuỗi, bytes hoặc Response(body, status=200, headers={...}). Runtime chuyển Content-Type và các header hợp lệ trong giới hạn (tối đa 40 header, mỗi giá trị tối đa 2.048 ký tự). Cookie, header kết nối, xác thực, proxy, CORS, Cache-Control và header nội bộ WhyPik không do mã ứng dụng quyết định; không thể tự đặt cookie nền tảng.
from whypik import api, db, Request, Response
# Tạo bảng products với các cột id, name trước khi chạy ví dụ.
@api.get("/products/{id}")
async def product(request: Request):
if request.user is None or not request.user.is_admin:
return Response({"error": "forbidden"}, status=403)
row = await db.table("products").filter(id=request.params["id"]).first("id", "name")
return row if row else Response({"error": "not_found"}, status=404)
@api.post("/echo")
async def echo(request: Request):
return Response(await request.json(), status=201)Đọc và phân trang
table(name) chọn bảng hiện có. filter(mapping, **fields) dùng eq, gt, gte, lt, lte, in, icontains, isnull và đường quan hệ dùng __. order_by("-field") giảm dần. select(*columns) trả một trang dòng; first trả một dòng hoặc None; page giữ envelope phân trang.
include(mapping) lấy quan hệ giống Data API. Các modifier tạo query mới, không đổi query cũ; không tự đọc hết các trang. Ví dụ include({"server":{"columns":["id","name"]}}).
players = await db.table("players").filter(server__id="1").order_by("-modified", "id").limit(20).select("id", "name")
player = await db.table("players").filter(id="1").first()
page = await db.table("players").limit(20).page()
if page["page"]["next_cursor"]:
next_page = await db.table("players").limit(20).cursor(page["page"]["next_cursor"]).page()Thêm, sửa, xóa
insert/update/delete trả data, affected, revision, request_id. update/delete cần điều kiện rõ ràng trên bảng gốc. returning chỉ lấy các cột được phép. Dùng chuỗi cho bigint/numeric để giữ độ chính xác.
Từng lệnh await là một giao dịch Data API riêng. SDK không tự retry thao tác ghi. MiniError.outcome_unknown nghĩa là có thể đã ghi: kiểm tra khóa nghiệp vụ duy nhất trước khi gửi lại; timeout không đồng nghĩa rollback.
created = await db.table("products").insert({"name":"New"}, returning=["id"])
changed = await db.table("products").filter(id="1").update({"name":"Changed"})
deleted = await db.table("products").filter(id="1").delete()Quyền truy cập và giới hạn
db trong Mini Server có quyền Admin dữ liệu của đúng Project: bỏ qua policy client/server:read/server:write ở bảng, dòng và cột. Không có raw SQL, chọn Project khác hoặc lấy thông tin kết nối PostgreSQL. Database vẫn kiểm cấu trúc, ràng buộc, quota và trạng thái dịch vụ. Client SDK vẫn giữ phân quyền như trước.
Quyền Database của backend không thay đổi theo người gọi HTTP. Handler phải kiểm request.user và điều kiện nghiệp vụ trước khi đọc/ghi/trả dữ liệu. Không có token thì request.user=None; token sai/hết hạn/thu hồi bị từ chối trước khi chạy. Snapshot là danh tính tại lúc nhận request, không tự cập nhật giữa handler.
Không gọi Database khi import module hoặc kiểm tra triển khai. Mỗi invocation tối đa 16 lệnh Data API; request/phản hồi tối đa 1 MB. Mặc định sandbox 256 MB, tối đa 60 giây từ lúc được cấp lượt thực thi (gồm khởi tạo, handler, Database và HTTPS); thời gian chờ cấp lượt được tính riêng. Số request đồng thời và ngân sách tháng là các giới hạn riêng theo gói. Quá thời gian trả mini_timeout (HTTP 504); kiểm tra kết quả thao tác ghi trước khi gửi lại. HTTPS công khai đi qua proxy chỉ được cấp quyền trong thời gian invocation; db dùng broker Data API của Project. Xem bài Gọi HTTPS và dịch vụ bên ngoài về địa chỉ, giao thức và hạn mức.
Local run dùng WHYPIK_ADMIN_KEY để gọi Database trên WhyPik. Muốn kiểm token người gọi và có request.user, thêm WHYPIK_PUBLIC_KEY và WHYPIK_AUTH_ORIGIN đã nằm trong Authentication → Cài đặt → Tên miền ứng dụng được phép. Thiếu cấu hình Auth thì request có Bearer bị từ chối; không tự coi token chưa kiểm là hợp lệ. Các khóa WHYPIK_* được giữ trong broker local, không truyền vào tiến trình handler. WHYPIK_CLOUD_KEY/WHYPIK_DEPLOY_KEY chỉ dùng quản lý/triển khai.
Vòng đời sandbox và bộ nhớ
WhyPik có thể giữ sandbox của cùng backend, phiên bản triển khai và revision Variables để dùng cho lượt gọi tiếp theo. Các backend không dùng chung module Python, biến toàn cục hoặc thư mục tạm. Snapshot nền chỉ chứa Python và SDK trước khi nạp mã, Variables và dữ liệu của khách.
Sau response, mã ứng dụng trong sandbox được tạm dừng bằng gVisor và kết nối HTTPS bị đóng. Khi cần nhường tài nguyên, WhyPik hủy sandbox ít dùng và khởi tạo lại khi có request. Biến toàn cục, module đã import và /tmp có thể còn giữa các lượt gọi nhưng cũng có thể mất bất cứ lúc nào. Không dùng chúng thay Database hoặc Object Storage; không lưu request.user, token hoặc dữ liệu riêng của người dùng vào biến toàn cục.
Mỗi lượt có event loop mới. Tạo và đóng AsyncClient hoặc SDK AI bất đồng bộ bên trong handler bằng async with. Không dùng task/thread nền để tiếp tục công việc sau response; chúng có thể bị dừng hoặc hủy. Client HTTP đồng bộ có thể giữ ở module, nhưng kết nối cũ không được giữ mở giữa các lượt.
Hết lượt đồng thời của tài khoản hoặc node, request chờ trong hàng đợi có giới hạn. Hàng đợi đầy/hết thời gian trả 503 với mini_queue_full, mini_queue_memory hoặc mini_queue_timeout trước khi chạy handler. Đây không phải giới hạn request/giây mới. Nếu kết quả là unknown, kiểm tra dữ liệu trước khi gửi lại thao tác ghi; không tự retry.
Đổi Variables hoặc đổi phiên bản khiến lượt gọi mới dùng sandbox có cấu hình mới; lượt đã chạy giữ snapshot cấu hình của nó. Request đang chờ với cấu hình cũ có thể bị từ chối trước thực thi. Đây là tối ưu thời gian khởi động, không phải cam kết 10.000 backend chạy đồng thời trên một VPS.
API tải ZIP có thể tiếp tục
Dùng Bearer API key có mini:write hoặc khóa CI của đúng backend. Tiền tố /api/v1/cloud/projects/PROJECT_UUID/mini; mọi request bên dưới dùng query ?service_id=SERVICE_UUID. Giữ nguyên actor/API key từ lúc tạo đến hoàn tất. Khóa bị thu hồi/hết hạn không được tải tiếp. GET tại tiền tố này công bố upload_transport=chunks-v1 cho cả khóa quản lý và khóa CI của backend.
POST /deployments với Content-Type: application/json, Idempotency-Key: UUID và {"transport":"chunks-v1","size":SIZE,"sha256":"SHA256_ZIP_GOC"}. Trả 201 {upload:{id,deployment_id,state,size,sha256,transport,chunk_size,chunk_count,expires_at,parts}}. parts là các biên nhận {index,size,sha256}; chunk_size=262144. POST cùng key/nội dung trả cùng lượt; key có nội dung/credential khác trả 409.
GET /uploads/UPLOAD_UUID trả {upload}. PUT /uploads/UPLOAD_UUID/parts/INDEX nhận bytes application/octet-stream, Content-Length chính xác và X-Chunk-SHA256. Chỉ số từ 0; phần cuối có thể ngắn hơn. Trả 200 {upload}; gửi lại phần cùng checksum không ghi đè. Sai checksum, kích thước hoặc phần đã có nội dung khác bị từ chối.
POST /uploads/UPLOAD_UUID/complete với {} trả 202 {deployment} sau khi đủ phần, SHA-256 khớp và ZIP hợp lệ. state pending nhận phần, receiving đã ghép và chỉ cần gọi complete, completed trả lại deployment. Mất phản hồi complete thì gọi lại cùng URL, không tạo bản mới. Kiểm GET /deployments/DEPLOYMENT_UUID đến ready và active=true; 202 không có nghĩa đã chạy.
Lượt tải tối đa 48 giờ; phiên bản đang tải vẫn chiếm lịch sử và dung lượng. DELETE /deployments/DEPLOYMENT_UUID bằng quyền quản lý hủy lượt. Khóa CI không được xóa hay sửa cấu hình. API multipart cũ vẫn hoạt động, nhưng không tiếp tục được từng phần. Giới hạn ZIP/mã nguồn giữ nguyên 2 MiB/1 MiB, 64 file.