Skip to content

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).

Terminal window
pip install "vaifstudio==1.0.0"
import asyncio
from 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.

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 locally

The 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(...).

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.

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 minutes

upload 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.

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.

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 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.

  • Realtime. Use @vaifstudio/client for realtime.
  • A synchronous client, Google and GitHub sign-in, magic links, and creating or deleting buckets.
  1. Uninstall vaif-client.
  2. Install vaifstudio as shown above.
  3. Change import vaif and from vaif import ... to import vaifstudio and from 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.