Python SDK reference
This page lists the public API of taruvi 0.2.3: each module, its methods, and
the HTTP request each method sends. The sync and async clients have the same
modules and methods. In the async client, every method that sends a request is
a coroutine; builder methods such as filter() are not.
Request paths are relative to your site address, and APP_SLUG is the client's
app_slug. Most module methods also accept app_slug= to target another app on
the same site. For setup, start with Python SDK.
Client#
Client(api_url, app_slug, ...) holds your site address, your app, and the
credential. Its modules, such as client.database, send the requests.
import os
from taruvi import Client
client = Client(
os.environ["TARUVI_SITE_URL"],
os.environ["TARUVI_APP_SLUG"],
mode="sync",
timeout=120,
max_retries=3,
api_key=os.environ["TARUVI_API_KEY"],
)
Arguments
api_url(required) — Your site address,TARUVI_SITE_URL. A trailing slash is removed.app_slug(required) — The app's slug,TARUVI_APP_SLUG.mode—"sync"or"async". Detected from the running event loop when omitted.timeout— Seconds to wait for each request: a whole number from 1 to 300.max_retries— Extra attempts after a timeout or connection failure. See Timeouts and retries.api_key,jwt,session_token— The credential. Passing one replaces any in the environment; see Authenticate with the Python SDK.
Members
database,storage,functions,secrets,policy,users,app,settings,analytics,auth— The modules below.is_authenticated— Whether the client holds a credential.config— The resolvedTaruviConfig.close()— Closes the HTTP connection pool. It also runs when awithorasync withblock exits.
Every request identifies the SDK with X-Taruvi-Client and User-Agent
headers, such as taruvi-python/0.2.3 (python/3.12.4).
Auth#
client.auth signs in and reads the current user. The sign-in methods return a
new client and leave the original unchanged.
Sign in with a password#
import os
user_client = client.auth.signInWithPassword(
os.environ["TARUVI_USER_EMAIL"],
os.environ["TARUVI_USER_PASSWORD"],
)
Returns a new client that sends the returned JWT. Raises
AuthenticationError when sign-in fails.
Sign in with a token#
import os
session_client = client.auth.signInWithToken(
os.environ["TARUVI_SESSION_TOKEN"],
token_type="session_token",
)
token_type is "jwt" (the default), "api_key", or "session_token".
Returns a new client with that credential. It sends no request and isn't a
coroutine.
Sign out#
signOut() returns a copy of the client with no credential. It sends no
request and isn't a coroutine.
Get the current user#
me = client.auth.get_current_user()
print(me["data"]["username"])
Returns the response envelope, with the user under ["data"].
Database#
client.database.from_(table) returns a query builder. The builder is mutable:
each method updates it and returns it. execute(), first(), and count()
send the request. Request paths below are relative to
/api/apps/APP_SLUG/datatables/TABLE/.
Call an operation such as get(), create(), or delete() last, right before
execute(). Invalid use, such as update() without a record ID, raises
ValueError before anything is sent.
List records#
Reads the records that match the query. Always set a page size: without
page_size(), a list read returns every matching row and page() has no
effect.
(
client.database.from_("tasks")
.sort("title", "asc")
.page(1)
.page_size(20)
.execute()
)
sort(field, order="asc")— Adds a sort key; call it again to add a tie-breaker. Sent asordering.order_by(*fields)— Several keys at once:order_by(("title", "desc"), "id")ororder_by("-title", "id").page_size(n)— Records per page, up to 1,000 by default; a larger value raisesValidationError.page(n)— The page number, starting at 1.
Returns {"data": [...], "total": N}, where total counts every match.
Filter records#
filter(field, operator, value) adds a condition. eq sends the bare field
name, and other operators send field__operator. List values for in, nin,
between, and similar operators are joined with commas. Repeating the same
field and operator keeps only the last value.
(
client.database.from_("tasks")
.filter("done", "eq", False)
.filter("title", "icontains", "docs")
.execute()
)
For or conditions and nesting, pass a filter tree, as a dict or list. It's
sent as JSON in filters, and TaruviBase combines it with any flat filters
using AND:
(
client.database.from_("tasks")
.filter([
{
"operator": "or",
"value": [
{"field": "done", "operator": "eq", "value": False},
{"field": "title", "operator": "icontains", "value": "urgent"},
],
}
])
.execute()
)
The operators are the same as in the JavaScript SDK; see Choose a filter operator.
Expand related records#
client.database.from_("tasks").populate("assignee", "project").execute()
populate(*fields) expands the named relationships, and populate_all()
expands every first-level relationship with populate=*. See
Relationships.
Search#
client.database.from_("tasks").search("invoice").execute()
Full-text search, on tables that have a search vector.
Vector and hybrid search#
query_vector = [0.12, -0.03, 0.88]
(
client.database.from_("documents")
.vector_search("embedding", query_vector, topk=5)
.execute()
)
vector_search(field, query_vector, *, topk=10, threshold=None, ef_search=None, metric=None)— Nearest-neighbour search on a vector field.thresholddrops results beyond that distance, andmetricis"cosine","l2", or"ip".hybrid(*, strategy="rrf", alpha=0.5)— Combinesvector_search()withsearch().alpharuns from0.0, text only, to1.0, vectors only.
See Search.
Aggregate#
(
client.database.from_("tasks")
.aggregate("count(*)")
.group_by("done")
.having("count__gte=10")
.execute()
)
aggregate(*expressions)— Aggregates such as"count(*)"or"sum(total)".group_by(*fields)— Groups the results.having(condition)— Filters the groups by an aggregate alias, such as"count__gte=10".
See Aggregations.
Include allowed actions#
client.database.from_("tasks").allowed_actions(["update", "delete"]).execute()
Each row gains _allowed_actions: the ones among update and delete that the
caller may perform on it.
Get a record#
client.database.from_("tasks").get("TASK_ID").execute()
Returns the response envelope, with the record in data. A missing record
raises NotFoundError.
Get the first record or a count#
client.database.from_("tasks").filter("done", "eq", False).first()
client.database.from_("tasks").filter("done", "eq", False).count()
first()— The first matching record, orNone. It requests one row.count()— The number of matching rows. It requests one row and readstotal.
Create records#
(
client.database.from_("tasks")
.create([
{"title": "Write release notes", "done": False},
{"title": "Ship docs", "done": False},
])
.execute()
)
body is a dict or a list of dicts. Returns the response envelope; data
is a list of the created records.
Upsert records#
(
client.database.from_("tasks")
.upsert([{"title": "Ship docs", "done": True}], unique_fields=["title"])
.execute()
)
Inserts records, or updates those whose unique_fields match, by default the
primary key. Returns the envelope; data is {"records": [...], "count": N}.
Update records#
client.database.from_("tasks").get("TASK_ID").update({"done": True}).execute()
After get(id), update(body) changes that record. Pass a list of records,
each with its primary key, to update several with PATCH …/data/. Returns
the response envelope.
Delete records#
client.database.from_("tasks").delete("TASK_ID").execute()
delete(id)— Deletes one record and returns{}.delete(ids)orbulk_delete(ids)— Deletes records by ID, withDELETE …/data/?ids=.delete_filtered()— Deletes every record that matches the query's filters, withDELETE …/data/?filter=. RaisesValueErrorwhen no filter is set.
Bulk and filtered deletes return {"deleted_count": N, "message": ...}. When
nothing matches, delete_filtered() returns the envelope, with the count in
deleted_count inside data.
delete_filtered() removes every matching record in one request and can't be
undone. Run the same filters as a read first and check total.
Graphs and edges#
(
client.database.from_("folders")
.get("ROOT_ID")
.include("descendants")
.depth(2)
.format("tree")
.execute()
)
include(direction)—"descendants","ancestors", or"both".depth(n)— The maximum traversal depth.format(format_type)—"flat","tree", or"graph".types(relationship_types)— The relationship types to follow.
edges() targets the table's edge table, TABLE_edges; create(), update(),
and delete() then act on edges. See
Graphs and hierarchies.
Module shortcuts#
For one-off calls, the module has shortcuts that return the record itself rather than the envelope:
client.database.get("tasks", "TASK_ID")
client.database.create("tasks", {"title": "Ship docs", "done": False})
client.database.update("tasks", "TASK_ID", {"done": True})
client.database.delete("tasks", "TASK_ID")
get(table, record_id)— The record.create(table, data)— The created records, as a list.update(table, record_id, data)— The updated record. With a list of records instead ofrecord_id, updates them all and returns{"records": [...], "count": N}.delete(table, record_id=None, *, ids=None, filter=None)— Pass exactly one of the three.
Storage#
client.storage.from_(bucket) selects a bucket; the methods below run on it.
Object paths may contain /. Request paths are relative to
/api/apps/APP_SLUG/storage/buckets/BUCKET/objects/.
List objects#
(
client.storage.from_("documents")
.filter(search="q3", mimetype="application/pdf", page=1, page_size=50)
.list()
)
filter() takes page, page_size, search, mimetype,
mimetype_category, visibility, ordering, and any other
storage filter as a keyword.
Returns the response envelope, with the objects in data and the count in
total.
Browse folders#
listing = client.storage.from_("documents").browse(prefix="reports/", sort="name")
print(listing["folders"], listing["objects"], listing["has_next"])
Takes prefix, page (default 1), page_size (default 50, up to 100),
sort, and order. Returns the folders and files directly under
prefix.
Upload files#
Uploads up to 100 files, 100 MB in total, in one request.
with open("q3.pdf", "rb") as report:
result = client.storage.from_("documents").upload(
files=[("q3.pdf", report)],
paths=["reports/q3.pdf"],
metadatas=[{"owner": "finance"}],
)
print(result["uploaded_count"], result["failed"])
files(required) — Each entry is(filename, file_obj)or(filename, file_obj, content_type). Without a content type, the SDK guesses it from the filename.paths(required) — The object path for each file, in the same order.metadatas— Metadata for each file, in the same order.
Returns uploaded_count, failed_count, successful, and failed. An
upload in which some files fail doesn't raise: check failed_count after every
upload.
Download a file#
client.storage.from_("documents").download("reports/q3.pdf")
Returns the file as bytes. A failed download raises the matching SDK
error.
Update metadata#
client.storage.from_("documents").update(
"reports/q3.pdf",
metadata={"owner": "finance", "status": "final"},
)
metadata replaces the object's metadata. Returns the object. An object's
visibility always follows its bucket; TaruviBase ignores a visibility passed
here.
Delete objects#
result = client.storage.from_("documents").delete(["reports/q1.pdf", "reports/q2.pdf"])
print(result["deleted_count"], result["failed"])
Returns deleted_count and failed. Objects that are missing, or that a
policy doesn't let the caller delete, are listed in failed rather than
raising.
Copy and move objects#
client.storage.from_("documents").copy_object(
"reports/q3.pdf",
"archive/2026/q3.pdf",
)
copy_object(source_path, destination_path, destination_bucket=None)— Copies the object, into another bucket whendestination_bucketis set. Returns the new object.move_object(source_path, destination_path)— Moves or renames it within the bucket, withPOST …/objects/move/. Returns the moved object.
Get an Office link#
For buckets backed by SharePoint, view_access(file_path) and
edit_access(file_path) return a link that opens an Office file, with
GET …/objects/PATH/view/ or …/edit/. Editing always needs a signed-in user.
Manage buckets#
The module itself manages buckets: list_buckets(), create_bucket(name, …),
get_bucket(slug), update_bucket(slug, …), and delete_bucket(slug).
delete_bucket() also deletes every object in the bucket. See
Manage buckets.
Functions#
client.functions runs your app's functions and reads their runs.
Run a function#
client.functions.execute("send-report", {"month": "2026-09"})
function_slug(required) — The function's slug.params— The function's input.is_async—Truequeues the run and returns right away;Falsewaits for the result. Leave it unset to use the function's own mode.timeout— Seconds to wait for this call, overriding the client's timeout.
Returns the response envelope: data is the function's return value, and
invocation describes the run.
Get a queued run's result#
A queued run returns at once with an empty data. Read the result later with
its task ID:
run = client.functions.execute("send-report", {"month": "2026-09"}, is_async=True)
task_id = run["invocation"]["celery_task_id"]
client.functions.get_result(task_id)
See Execute functions.
List functions and runs#
list(*, limit=100, offset=0)— The app's functions, withGET /api/apps/APP_SLUG/functions/.get(function_slug)— One function.list_invocations(*, function_slug=None, status=None, limit=100, offset=0)— Past runs, withGET /api/invocations/.get_invocation(invocation_id)— One run.
These need a TaruviBase Console user with an elevated role; app users can only run functions.
Secrets#
client.secrets reads secrets that the caller may see. When app is
omitted, the client's app_slug is used.
Get a secret#
secret = client.secrets.get("app_config")
print(secret["value"])
key(required) — The secret's key.app— An app slug, to read that app's secret.tags— Tags the secret must have; if it has none of them, the call raisesNotFoundError.
Returns the secret: its key, value, tags, and secret_type.
Get or list several secrets#
client.secrets.list(keys=["app_config", "feature_flags"])
With keys, data maps each key to its value, or to
{value, tags, secret_type} with include_metadata=True; keys that are
missing or not readable are left out. Without keys, list() returns a page
of secrets filtered by search, app, tags, and secret_type, with
page and page_size (up to 100). See
Read secrets from code.
Policy#
client.policy asks TaruviBase what the caller may do. Checks always run as
the client's caller.
Check resources#
result = client.policy.check_resources([
{
"resource": {"kind": "datatable:tasks", "id": "TASK_ID", "attr": {}},
"actions": ["update", "delete"],
}
])
can_delete = result["results"][0]["actions"]["delete"] == "EFFECT_ALLOW"
Returns one result per entry, mapping each action to EFFECT_ALLOW or
EFFECT_DENY. Pass aux_data by keyword: the deprecated principal argument
comes before it, and TaruviBase rejects any principal with a 400.
Filter by permission#
tables = [
{"kind": "datatable:tasks", "id": "TASK_ID_1"},
{"kind": "datatable:tasks", "id": "TASK_ID_2"},
]
client.policy.filter_allowed(tables, ["update"])
filter_allowed(resources, actions)— The resources on which every action is allowed.get_allowed_actions(resource, actions=None)— The allowed action names for one resource.actionsdefaults toread,write,create,update, anddelete.
Both take resources as {"kind": ..., "id": ...}. See
Runtime permission checks.
Users#
client.users manages your site's users. Any signed-in caller can list and
read them. Creating, updating, and deleting users needs organization access (an organization owner, admin, or member, or an API key one of them created) or a Super Admin
app role in one of the site's apps. Anyone else gets 403 with code
FORBIDDEN, raised as AuthorizationError. A Super Admin app role can't
change superusers or members of your organization. See
Site users.
List and get users#
result = client.users.list(search="ada", is_active=True, page_size=20)
for user in result["data"]:
print(user["username"])
list(**filters) takes search, is_active, is_superuser, is_deleted,
is_cloud_user, roles (comma-separated slugs), ordering, page, and
page_size, and returns the envelope with data and total. Without filters,
the list returns active users that aren't deleted, and leaves out TaruviBase
cloud users and superusers; a filter you pass replaces its default.
get(username) returns one user, with GET /api/users/USERNAME/.
Create, update, and delete users#
client.users.create({
"username": "ada",
"password": "CHOOSE_A_PASSWORD",
"confirm_password": "CHOOSE_A_PASSWORD",
"first_name": "Ada",
"last_name": "Lovelace",
})
create(data)— Creates a user.username,email,password,confirm_password,first_name, andlast_nameare required.update(username, data)— Changes only the fields you pass, withPUT /api/users/USERNAME/. TaruviBase ignoresis_staff,is_superuser, andis_cloud_useron create and update.delete(username)— Soft-deletes the user, withDELETE /api/users/USERNAME/: TaruviBase marks them deleted and inactive.apps(username)— The user's apps, withGET /api/users/USERNAME/apps/.
Assign and revoke roles#
result = client.users.assign_roles(
roles=["editor"],
usernames=["ada"],
expires_at="2026-12-31T23:59:59Z",
)
print(result["message"])
assign_roles(roles, usernames, expires_at=None)— Gives every user inusernamesevery role inroles, untilexpires_atwhen it's set.revoke_roles(roles, usernames)— Removes them, withDELETE /api/revoke/roles/.
Both need organization access (an organization owner, admin, or member, or an API key one of them created), and take up to 100 roles and 100 usernames. Assignments that already exist,
or when revoking don't exist, are skipped and count as success. Returns a
message, plus data.failures when some changes failed.
Read and update preferences#
client.users.update_preferences({"theme": "dark", "timezone": "Europe/London"})
get_preferences() reads the caller's preferences with
GET /api/users/me/preferences/. update_preferences(data) changes only the
fields you pass: date_format, time_format, timezone, theme, or
widget_config.
App, settings, and analytics#
Read app roles and settings#
client.app.roles()
app.roles() lists the app's roles, and app.settings() reads its settings
with GET /api/apps/APP_SLUG/settings/.
Read public site settings#
client.settings.get()
Works without a credential. Returns the site's domain and its public
settings, such as theme and branding.
Run a saved query#
client.analytics.execute("revenue-by-month", {"year": 2026})
Returns the response envelope, with the query's result in data. See
Execute saved queries.
A run that fails raises ValidationError with status 400 and code
"BAD_REQUEST"; the cause is in the message. See
Error categories.
Errors#
Import every class from taruvi. API errors carry message, status_code,
code, detail, and details, and to_dict() returns them as a dict.
NotAuthenticatedError carries message, status_code, and the response's
code ("UNAUTHORIZED") and detail; its message says to sign in first.
| Class | Raised for |
|---|---|
ValidationError | 400; details holds field errors |
AuthenticationError | 401 with a credential |
NotAuthenticatedError | 401 from a client with no credential |
BillingError | 402, 429, or 503 when billing blocked the request |
AuthorizationError | 403 |
NotFoundError | 404 |
ConflictError | 409 |
RateLimitError | 429 |
ServerError | 500 |
ServiceUnavailableError | 503 |
GatewayTimeoutError | 504 |
APIError | Any other 4xx or 5xx |
TimeoutError | No response within timeout, after any retries |
ConnectionError | The connection failed or dropped, after any retries |
ResponseError | The response wasn't valid JSON |
ConfigurationError | api_url or app_slug is missing |
TaruviError is the base class of every SDK error. APIError is the base of
every error with an HTTP status, and NetworkError of TimeoutError and
ConnectionError. NotAuthenticatedError is a subclass of
AuthenticationError.
BillingError means the organization's billing blocked the request: the
account isn't active (account_suspended), the plan's usage for module is
used up for this period (product_suspended), or billing status couldn't be
read (gate_unavailable). Only gate_unavailable is worth retrying; check
retryable.
ValueError is raised for invalid arguments, such as update() without a
record ID or delete_filtered() without a filter.