Skip to content

Python SDK

Package: kataflax-sdk (import kataflax). Same surface as @kataflax/sdk and kataflax_client_sdk: project auth, collections, queries, files. It does not manage orgs or schemas.

Requires Python 3.11+ and httpx (>=0.27). Calls are synchronous. Filter, join, expand, and upload arguments are keyword-only. There is no await.

kataflax-sdk is not on PyPI. Install the package in editable mode from this repo:

Terminal window
pip install -e packages/kataflax_sdks/python/sdk
from kataflax import KataflaxClient, KataflaxException, Null
from kataflax import KataflaxClient, KataflaxException
client = KataflaxClient(
base_url="https://api.tefily.com",
project_id="YOUR_PROJECT_ID",
)
auth = client.auth.login(email="user@example.com", password="password")
print(f"Logged in as {auth.user['email']}")
posts = client.collection("posts")
created = posts.create({"title": "Hello World", "status": "draft"})
many = posts.create_many(
[
{"title": "Hello", "status": "draft"},
{"title": "World", "status": "draft"},
]
)
all_posts = posts.list(page=1, page_size=20)
post = posts.get(created["_id"])
posts.update(post["_id"], {"title": "Updated title"})
posts.delete(post["_id"])
client.close()

Inject an httpx.Client in tests:

client = KataflaxClient(
base_url="https://api.tefily.com",
project_id="YOUR_PROJECT_ID",
http_client=mock_client,
)
client.auth.register(
email="jane@example.com",
password="secret123",
extra_fields={"name": "Jane"},
)
# Confirm the email via the Tefily link, then:
client.auth.login(email="jane@example.com", password="secret123")
print(client.token)
client.set_token("eyJhbGciOi...")
profile = client.auth.me()

set_token stores a platform JWT that bypasses end-user rules, or a personal access token. Client apps should use project login.

End-user auth covers the confirmation email, password length, and the fact that register does not return a JWT.

resend_verification, verify_email, request_password_reset, and complete_password_reset are on auth.

A client with a service_token can send mail from the project’s own address with email.send. See Send email.

backend.email.send(
to="jane@example.com",
subject="Your order shipped",
html="<p>It is on the way.</p>",
reply_to="help@example.com",
)

Provide html, text, or both. The From address is fixed by the project.

A backend client takes service_token= and calls auth.verify. That sample is on Service tokens. login on that client replaces kfxs_… with a project JWT.

Chained where is $and. Use Null from kataflax when the filter must be JSON null (is_equal_to=Null). Python None omits the operator.

from and as are syntax keywords, so join uses from_collection and as_name. foreign_field defaults to "_id".

Keyword Mongo operator
is_equal_to $eq
is_not_equal_to $ne
is_greater_than $gt
is_greater_than_or_equal_to $gte
is_less_than $lt
is_less_than_or_equal_to $lte
is_in $in
is_not_in $nin
matches $regex with $options: "i"
from kataflax import Null
results = (
client.collection("products")
.where("price", is_greater_than_or_equal_to=10)
.where("price", is_less_than=100)
.where("category", is_in=["electronics", "books"])
.order_by("price")
.order_by("_createdAt", descending=True)
.list(page_size=50)
)
drafts = client.collection("posts").where("deletedAt", is_equal_to=Null).list()

order_by("name") is ascending (1); descending=True is -1.

select(["title"]) returns only those fields (plus _id).

join("authorId", from_collection="users", as_name="author") embeds matching related documents as an array under author (default limit 50, max 100).

expand("authorId") uses a reference field’s optional target collection. expand takes as_name, fields, and limit as keywords.

select, join, and expand are chainable on Query (and as Collection entry points). Collection.get(id) returns the full document. Query.get(id) is the call that honors select, join, and expand.

Query documents caps, $and, how list rules apply to joins, and bulk validation. The samples on that page use JavaScript and Dart spellings.

update_many and delete_many require at least one where. They use the same encoded filter as list.

modified = (
client.collection("posts")
.where("status", is_equal_to="draft")
.update_many({"status": "archived"})
)
deleted = (
client.collection("posts")
.where("status", is_equal_to="archived")
.delete_many()
)

Query documents the 100-item cap and the “at least one where” rule. The samples there use JavaScript and Dart names (updateMany, deleteMany). create_many is in the quickstart.

upload_file accepts bytes.

meta = posts.upload_file(
record_id,
"attachment",
data=photo_bytes,
size=len(photo_bytes),
filename="photo.jpg",
content_type="image/jpeg",
)
download = posts.get_download_url(record_id, "attachment", meta.id)
posts.delete_file(record_id, "attachment", meta.id)

Files covers the presign, PUT, and complete steps. The samples on that page are JavaScript and Dart.

try:
client.collection("posts").get("nonexistent")
except KataflaxException as exc:
print(exc.status_code, exc.message, exc.errors)