Python SDK
The Python SDK is an async client for VAIF Studio: sign your app’s users in, query tables, store files and invoke functions. It needs Python 3.10 or later, is fully typed, and has one runtime dependency (httpx).
Install
Section titled “Install”pip install "vaifstudio==1.0.0"Quick start
Section titled “Quick start”import asynciofrom vaifstudio import create_client
async def main() -> None: async with create_client("YOUR_PROJECT_ID", "vk_pub_...") as vaif: await vaif.auth.sign_in("ada@example.com", "a strong password")
todos = await vaif.from_("todos").eq("done", False).order("created_at", desc=True).limit(20).list() print(todos.count, todos.data)
await vaif.from_("todos").insert({"title": "Buy milk"})
asyncio.run(main())The second argument is a project API key, sent in the X-Vaif-Key header. Use a publishable vk_pub_ key in apps. A secret vk_srv_ key bypasses row rules and belongs on trusted servers only.
Configuration
Section titled “Configuration”from vaifstudio import VaifClient, RetryPolicy
vaif = VaifClient( "YOUR_PROJECT_ID", "vk_pub_...", base_url="https://api.vaif.studio", # https required; http only for localhost timeout=30.0, # seconds, per request retry=RetryPolicy(max_retries=3, base_delay=1.0, max_delay=30.0),)GET, HEAD and OPTIONS requests are retried on 429, 500, 502, 503 and 504 responses and on network errors, and Retry-After is honored. Writes are never replayed once they may have reached the server.
result = await vaif.auth.sign_up("ada@example.com", "pw", metadata={"plan": "free"})if result.pending_verification: print(result.message)
result = await vaif.auth.sign_in("ada@example.com", "pw")if result.mfa_required: session = await vaif.auth.verify_mfa(result.challenge_token, input("code: "))
user = await vaif.auth.get_user()await vaif.auth.request_password_reset("ada@example.com")await vaif.auth.sign_out() # ends the session on the server, always clears it locallyThe access token is refreshed automatically 30 seconds before it expires, and once more if a request comes back 401. Sessions are kept in memory: to keep one across restarts, save it from vaif.auth.on_session_change(...) and restore it with vaif.auth.set_session(...).
Database
Section titled “Database”table = vaif.from_("todos")
rows = await table.select("id", "title").eq("owner", user.id).gte("priority", 2) \ .in_("status", ["open", "blocked"]).order("priority", desc=True).limit(50).list()
page = await table.eq("done", False).paginate(page=2, page_size=20)row = await table.get("row-id") # raises VaifNotFoundError if missing
created = await table.insert({"title": "Buy milk"})saved = await table.upsert({"email": "a@b.c", "name": "A"}, on_conflict=["email"])updated = await table.update("row-id", {"done": True})await table.delete("row-id")Filters are eq, neq, gt, gte, lt, lte, like, ilike and in_, and they must all match. A query returns at most 1000 rows.
Storage
Section titled “Storage”await vaif.storage.upload("avatars/u1.png", png_bytes, content_type="image/png")data = await vaif.storage.download("avatars/u1.png")url = await vaif.storage.create_signed_url("avatars/u1.png") # valid for 10 minutesupload sends the bytes straight to file storage through a signed URL; the API key and the session token are never sent to that URL. Without bucket=, the project’s default bucket is used.
Functions
Section titled “Functions”response = await vaif.functions.invoke("send-email", {"to": "ada@example.com"})print(response.status, response.data)A function that answers with an error status raises the matching VaifError, with the function’s body in error.body. Invocations are not retried unless you pass idempotent=True.
Errors
Section titled “Errors”from vaifstudio import VaifError, VaifRateLimitError
try: await vaif.from_("todos").eq("nope", 1).list()except VaifRateLimitError as e: print("retry after", e.retry_after)except VaifError as e: print(e.status, e.code, e.message, e.request_id)The subclasses are VaifValidationError (400, 422), VaifAuthError (401, 403), VaifNotFoundError (404), VaifConflictError (409), VaifRateLimitError (429), VaifServerError (5xx) and VaifNetworkError, with VaifTimeoutError under it.
Every other route
Section titled “Every other route”Every other API route is a generated method on vaif.operations, named after the route:
keys = await vaif.operations.get_projects_by_project_id_api_keys(path={"projectId": project_id})Console routes such as API keys need a console access token: pass headers={"Authorization": "Bearer <token>"}. See Access tokens.
Not in this client
Section titled “Not in this client”- Realtime. Use
@vaifstudio/clientfor realtime. - A synchronous client, Google and GitHub sign-in, magic links, and creating or deleting buckets.
Moving from the earlier package
Section titled “Moving from the earlier package”- Uninstall
vaif-client. - Install
vaifstudioas shown above. - Change
import vaifandfrom vaif import ...toimport vaifstudioandfrom vaifstudio import ….
The distribution and the import package are both vaifstudio. The bare vaif name on PyPI is an old generated client, not this package.