Appearance
Python client
kinedb-client is the direct client of kinedb for Python. It sends SQL to a running kinedb server over HTTP and over a WebSocket, with the same API as the JavaScript client.
Install
The packages live on the forge, under the kinedb organisation: https://git.kinedb.com/kinedb/-/packages. There is no pypi.org copy.
pip:
pip install --index-url https://git.kinedb.com/api/packages/kinedb/pypi/simple kinedb-clientuv, in the pyproject.toml of your application:
toml
[[tool.uv.index]]
name = "kinedb"
url = "https://git.kinedb.com/api/packages/kinedb/pypi/simple"
explicit = true
[tool.uv.sources]
kinedb-client = { index = "kinedb" }Use the forge as an EXPLICIT index, never --extra-index-url alone. All four kinedb names are free on pypi.org, so a second index may answer first and hand your application a stranger's package.
The index needs a Gitea token when the organisation is private. pip takes it in the URL (https://<user>:<token>@git.kinedb.com/...); uv takes UV_INDEX_KINEDB_USERNAME and UV_INDEX_KINEDB_PASSWORD.
Use
python
import asyncio
from kinedb.client import KineDB, rows_as_objects
async def main():
db = KineDB("http://localhost:4820")
print(await db.health())
print(await db.sql("SHOW DATABASES")) # one statement over HTTP
sock = await db.connect() # a persistent WebSocket
await sock.authenticate("root", "hunter2")
rows = await sock.sql("SELECT * FROM users")
print(rows_as_objects(rows))
sub = await sock.watch("users", lambda notify: print(notify))
await sub.cancel()
sock.close()
asyncio.run(main())base_url is required. Unlike the browser client, this one has no page to derive a URL from. ws_url is derived from base_url when it is not given: http becomes ws, https becomes wss, and /ws is appended to the path.
A result is a plain dict, tagged the way the server tags its own JSON: resp["type"] is rows, created, inserted, backoff, health and so on. A typed result set also carries resp["types"] and resp["schema_id"].
On integers. The JavaScript client hands back a BigInt above 2^53, because a JavaScript number cannot hold an integer exactly past that point. A Python int is unbounded, so that distinction has no Python half: an Int column decodes to a plain int and keeps every bit, always.
Credentials
The client holds no session. An application installs two hooks once, and every KineDB(...) in that application picks them up:
python
from kinedb.client import set_auth_hooks
set_auth_hooks(
get_token=lambda: store.token, # read on EVERY request, never cached
on_unauthorized=lambda: store.logout(), # runs on a 401, BEFORE the raise
)Either hook may be a coroutine function; the client awaits the result.
One client can override them, which is what a program that talks to a second server with a different credential needs:
python
KineDB(url, get_token=lambda: other_token)With neither, the client sends no bearer and bounces nobody. That is the right default for a consumer that never logs in. GET /health stays bare in every case, because it is open by design and a liveness probe has no credentials.
set_auth_hooks returns the hooks it replaced, so a test or a one-off task can put them back:
python
previous = set_auth_hooks(get_token=borrowed)
...
set_auth_hooks(**previous)Retrying
Off by default: a server-flagged transient rejection raises at once, with err.retryable set so a caller can build its own loop.
python
db = KineDB(url, retry=True)
db = KineDB(url, retry={"max_retries": 3, "base_delay_ms": 20, "max_delay_ms": 500})With it on, the client absorbs those rejections behind a random full-jitter wait, and honours the server's own retry_ms pacing hint on a backoff response. The CLIENT retries; the server never does.
No runtime dependencies
The package installs nothing else. The HTTP transport is urllib.request on a worker thread, and the WebSocket client is our own RFC 6455 codec over asyncio streams (kinedb.client.ws). An SDK that pulled websockets or httpx would force a version range on every application that installs it.
Pass http_request= or ws_connect= to KineDB(...) to drive it from a test with no server.